一、Spring Security
1、框架介绍
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security其实就是用filter,多请求的路径进行过滤。
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
2、认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问。
二、整合Spring Security
1、在common下创建spring_security模块
2、在spring_security引入相关依赖
pom.xml
common com.javaclimb 0.0.1-SNAPSHOT 4.0.0 spring_security com.javaclimb common_util 0.0.1-SNAPSHOT com.javaclimb service_base 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt
com.javaclimb spring_security 0.0.1-SNAPSHOT
4、代码结构说明
核心配置类:TokenWebSecurityConfig
用户实体类:User
登录过滤器:TokenLoginFilter
访问过滤器:TokenAuthenticationFilter
密码处理类:DefaultPasswordEncoder
登出业务逻辑类:TokenLogoutHandler
token操作工具类:TokenManager
未授权的统一处理方式:UnauthorizedEntryPoint
5、辅助类
(1) UserService.java
package com.stu.service.acl.service;import com.stu.service.acl.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;/*** * 用户表 服务类*
** @author stu* @since 2022-08-16*/
public interface UserService extends IService {/************************************ 用途说明:获取用户详情* @param username* 返回值说明:* @return com.stu.service.acl.entity.User***********************************/User selectByUserName(String username);
}
(2) UserServiceImpl.java
package com.stu.service.acl.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.mapper.UserMapper;
import com.stu.service.acl.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** * 用户表 服务实现类*
** @author stu* @since 2022-08-16*/
@Service
public class UserServiceImpl extends ServiceImpl implements UserService {/************************************ 用途说明:获取用户详情* @param username* 返回值说明:* @return com.stu.service.acl.entity.User***********************************/@Overridepublic User selectByUserName(String username) {return baseMapper.selectOne(new QueryWrapper().eq("username", username));}
}
package com.stu.service.acl.mapper;import com.stu.service.acl.entity.Permission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;import java.util.List;/*** * 权限 Mapper 接口*
** @author stu* @since 2022-08-16*/
public interface PermissionMapper extends BaseMapper {/************************************ 用途说明:获取全部菜单的权限* 返回值说明:* @return java.util.List***********************************/List selectAllPermissionValue();/************************************ 用途说明:根据用户id查询所有权限* @param id* 返回值说明:* @return java.util.List***********************************/List selectPermissionValueByUserId(String id);/************************************ 用途说明:根据用户id查询所有菜单* @param userId* 返回值说明:* @return java.util.List***********************************/List selectPermissionByUserId(String userId);
}
p.id,p.pid,p.name,p.type,p.permission_value,path,p.component,p.icon,p.status,p.is_deleted,p.gmt_create,p.gmt_modified
package com.stu.service.acl.service;import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.entity.Permission;
import com.baomidou.mybatisplus.extension.service.IService;import java.util.List;/*** * 权限 服务类*
** @author stu* @since 2022-08-16*/
public interface PermissionService extends IService {/************************************ 用途说明:查询所有权限菜单* 返回值说明:* @return java.util.List***********************************/List ListAllPermissions();/************************************ 用途说明:递归删除菜单* @param id* 返回值说明:* @return boolean***********************************/boolean removeChildById(String id);/************************************ 用途说明:根據角色獲取菜單* @param id* 返回值说明:* @return java.util.List***********************************/List listAllMenu(String id);/************************************ 用途说明:给角色分配菜单权限* @param roleId* @param permissionId* 返回值说明:* @return boolean***********************************/boolean saveRolePermissionrelationShip(String roleId, String[] permissionId);/************************************ 用途说明:根据用户id查询有权限的菜单* @param id* 返回值说明:* @return java.util.List***********************************/List selectPermissionValueListByUserId(String id);/************************************ 用途说明:根据用户id查询所有权限的菜单详细列表* @param userId* 返回值说明:* @return java.util.List***********************************/List selectPermissionByUserId(String userId);}
(6) PermissionServiceImpl.java实现类
package com.stu.service.acl.service.impl;import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.RolePermission;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.mapper.PermissionMapper;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.RolePermissionService;
import com.stu.service.acl.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** * 权限 服务实现类*
** @author stu* @since 2022-08-16*/
@Service
public class PermissionServiceImpl extends ServiceImpl implements PermissionService {@Autowiredprivate RolePermissionService rolePermissionService;@Autowiredprivate UserService userService;/************************************ 用途说明:查询所有权限菜单* 返回值说明:* @return java.util.List***********************************/@Overridepublic List ListAllPermissions() {QueryWrapper queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("id");return bulidPermission(baseMapper.selectList(queryWrapper));}/************************************ 用途说明:把返回所有菜单list集合进行封装的方法* @param list* 返回值说明:* @return java.util.List***********************************/private List bulidPermission(List list) {//创建list集合,用于数据最终封装List finalNode = new ArrayList<>();//把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1for (Permission permission : list) {//得到顶层菜单 pid=0菜单if ("0".equals(permission.getPid())) {permission.setLevel(1);//根据顶层菜单,向里面进行查询子菜单,封装到finalNode里面finalNode.add(selectChildren(permission, list));}}return finalNode;}/************************************ 用途说明:递归查询下级菜单* @param permission* @param list* 返回值说明:* @return com.stu.service.acl.entity.Permission***********************************/private Permission selectChildren(Permission permission, List list) {//1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化permission.setChildren(new ArrayList());//2 遍历所有菜单list集合,进行判断比较,比较id和pid值是否相同for (Permission child : list) {if (permission.getId().equals(child.getPid())) {child.setLevel(permission.getLevel() + 1);if (child.getChildren() == null) {child.setChildren(new ArrayList<>());}
// permission.getChildren().add(child);
// selectChildren(child,list);permission.getChildren().add(selectChildren(child, list));}}return permission;}/************************************ 用途说明:递归删除菜单* @param id* 返回值说明:* @return boolean***********************************/@Overridepublic boolean removeChildById(String id) {List idList = new ArrayList<>();selectChildListById(id, idList);idList.add(id);return baseMapper.deleteBatchIds(idList) > 0;}/************************************ 用途说明:根據角色獲取菜單* @param id* 返回值说明:* @return java.util.List***********************************/@Overridepublic List listAllMenu(String id) {//获取所有菜单List allPermissionList = baseMapper.selectList(new QueryWrapper<>());//根据角色id呼气角色权限列表List rolePermissionsList = rolePermissionService.list(new QueryWrapper().eq("role_id", id));//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记List permissionIdList = rolePermissionsList.stream().map(e -> e.getPermissionId()).collect(Collectors.toList());allPermissionList.forEach(permission -> {if (permissionIdList.contains(permission.getId())) {permission.setHasSelect(true);} else {permission.setHasSelect(false);}});/*for (int i = 0; i < allPermissionList.size(); i++) {Permission permission = allPermissionList.get(i);for (int m = 0; m < rolePermissionList.size(); m++) {RolePermission rolePermission = rolePermissionList.get(m);if(rolePermission.getPermissionId().equals(permission.getId())) {permission.setSelect(true);}}}*/return bulidPermission(allPermissionList);}/************************************ 用途说明:给角色分配菜单权限* @param roleId* @param permissionId* 返回值说明:* @return boolean***********************************/@Overridepublic boolean saveRolePermissionrelationShip(String roleId, String[] permissionId) {//删除旧的权限rolePermissionService.remove(new QueryWrapper().eq("role_id", roleId));List list = new ArrayList<>();for (String id : permissionId) {RolePermission rolePermission = new RolePermission();rolePermission.setRoleId(roleId);rolePermission.setPermissionId(id);list.add(rolePermission);}return rolePermissionService.saveBatch(list);}/************************************ 用途说明:根据用户id查询有权限的菜单* @param id* 返回值说明:* @return java.util.List***********************************/@Overridepublic List selectPermissionValueListByUserId(String id) {List list;if (checkAdmin(id)) {//如果是超级管理员获取所有权限list = baseMapper.selectAllPermissionValue();} else {//根据用户id查询所有权限list = baseMapper.selectPermissionValueByUserId(id);}return list;}/************************************ 用途说明:根据用户id查询所有权限的菜单详细列表* @param userId* 返回值说明:* @return java.util.List***********************************/@Overridepublic List selectPermissionByUserId(String userId) {List selectPermissionList = null;if (checkAdmin(userId)) {//如果是超级管理员获取所有权限selectPermissionList = baseMapper.selectList(null);} else {//根据用户id查询所有权限selectPermissionList = baseMapper.selectPermissionByUserId(userId);}//先转换成树状List permissionList = bulidPermission(selectPermissionList);//然后转化成前端需要的格式List result = bulidJson(permissionList);return result;}/************************************ 用途说明:转化成前端需要的格式* @param permissionList* 返回值说明:* @return java.util.List***********************************/private List bulidJson(List permissionList) {List menus = new ArrayList<>();if (permissionList.size() == 1) {Permission topNode = permissionList.get(0);//组建左侧一级菜单List oneMenuList = topNode.getChildren();for (Permission one : oneMenuList) {JSONObject oneMenu = new JSONObject();oneMenu.put("path", one.getPath());oneMenu.put("component", one.getComponent());oneMenu.put("redirect", "noredirect");//第一级不需要重定向oneMenu.put("name", "name_" + one.getId());oneMenu.put("hidden", false);//一级不需要因此,3级需要JSONObject oneMeta = new JSONObject();oneMeta.put("title", one.getName());oneMeta.put("icon", one.getIcon());oneMenu.put("meta", oneMeta);List children = new ArrayList<>();List twoMenuList = one.getChildren();//二级菜单for (Permission two : twoMenuList) {JSONObject twoMenu = new JSONObject();twoMenu.put("path", two.getPath());twoMenu.put("component", two.getComponent());
// twoMenu.put("redirect", "noredirect");//第一级不需要重定向twoMenu.put("name", "name_" + two.getId());twoMenu.put("hidden", false);//一级不需要因此,3级需要JSONObject twoMeta = new JSONObject();twoMeta.put("title", two.getName());twoMeta.put("icon", two.getIcon());twoMenu.put("meta", twoMeta);children.add(twoMenu);//功能按钮List threeMenuList = two.getChildren();for (Permission three : threeMenuList) {if (StringUtils.isEmpty(three.getPath())) {continue;}JSONObject threeMenu = new JSONObject();threeMenu.put("path", three.getPath());threeMenu.put("component", three.getComponent());
// threeMenu.put("redirect", "noredirect");//第一级不需要重定向threeMenu.put("name", "name_" + three.getId());threeMenu.put("hidden", true);//一级不需要因此,3级需要JSONObject threeMeta = new JSONObject();threeMeta.put("title", three.getName());threeMeta.put("icon", three.getIcon());threeMenu.put("meta", threeMeta);children.add(threeMenu);}}oneMenu.put("children", children);menus.add(oneMenu);}}return menus;}/************************************ 用途说明:判断是否管理员* @param id* 返回值说明:* @return boolean***********************************/private boolean checkAdmin(String id) {User user = userService.getById(id);if (user != null && "admin".equals(user.getUsername())) {return true;}return false;}/************************************ 用途说明:根据当前菜单id查询他的子子孙孙id,封装到list集合* @param id* @param idList* 返回值说明:***********************************/private void selectChildListById(String id, List idList) {//查询当前菜单的下级QueryWrapper queryWrapper = new QueryWrapper<>();queryWrapper.eq("pid", id);queryWrapper.select("id");List childList = baseMapper.selectList(queryWrapper);//把childIdList里面菜单id值获取出来,封装idList里面,做递归查询childList.forEach(item -> {idList.add(item.getId());selectChildListById(item.getId(), idList);});}}
package com.stu.service.acl.service.impl;import com.stu.security.entity.SecurityUser;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.UserService;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.ResultCodeEnum;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;/******************************* 用途说明:自定义UserDetailsService实现类,认证用户详情* 作者姓名: Administrator* 创建时间: 2022-09-01 10:05******************************/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserService userService;@Autowiredprivate PermissionService permissionService;/***根据用户名查询用户信息* @param username the username identifying the user whose data is required.* @return a fully populated user record (never null)* @throws UsernameNotFoundException if the user could not be found or the user has no* GrantedAuthority*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userService.selectByUserName(username);if(user == null){throw new CustomException(ResultCodeEnum.LOGIN_MOBILE_ERROR);}// 返回UserDetails实现类com.stu.security.entity.User curUser = new com.stu.security.entity.User();BeanUtils.copyProperties(user,curUser);//根据用户id查询有权限的菜单List authorities = permissionService.selectPermissionValueListByUserId(user.getId());SecurityUser securityUser = new SecurityUser(curUser);securityUser.setPermissionValueList(authorities);return securityUser;}
}
(8)ResponseUtil.java
package com.stu.service.base.utils;import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.service.base.result.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 20:44******************************/
public class ResponseUtil {public static void out(HttpServletResponse response, R r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}}
}
package com.stu.service.acl.service;import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.Role;
import com.baomidou.mybatisplus.extension.service.IService;import java.util.List;
import java.util.Map;/*** * 服务类*
** @author stu* @since 2022-08-16*/
public interface RoleService extends IService {/************************************ 用途说明:根据用户获取角色* @param userId* 返回值说明:* @return java.util.List***********************************/Map findRoleByUserId(String userId);/************************************ 用途说明:给用户分配角色权限* @param userId* @param permissionIds* 返回值说明:* @return boolean***********************************/boolean saveUserRelationShip(String userId, String[] permissionIds);/************************************ 用途说明:根据userid获取用户信息* @param id* 返回值说明:* @return java.util.List***********************************/List selectRoleByUserId(String id);
}
package com.stu.service.acl.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.Role;
import com.stu.service.acl.entity.RolePermission;
import com.stu.service.acl.entity.UserRole;
import com.stu.service.acl.mapper.RoleMapper;
import com.stu.service.acl.service.RoleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.service.acl.service.UserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** * 服务实现类*
** @author stu* @since 2022-08-16*/
@Service
public class RoleServiceImpl extends ServiceImpl implements RoleService {@Autowiredprivate UserRoleService userRoleService;/************************************ 用途说明:根据用户获取角色* @param userId* 返回值说明:* @return java.util.List***********************************/@Overridepublic Map findRoleByUserId(String userId) {//获取所有角色List allRoleList = baseMapper.selectList(new QueryWrapper<>());//根据用户id获取角色列表List existUserRoleList = userRoleService.list(new QueryWrapper().eq("user_id", userId).select("role_id"));//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记List existRoleLists = existUserRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList());List assignRoles = new ArrayList<>();allRoleList.forEach(role -> {if (existRoleLists.contains(role.getId())) {assignRoles.add(role);}});Map roleMap = new HashMap<>();roleMap.put("assignRoles", assignRoles);roleMap.put("allRoleList", allRoleList);return roleMap;}/************************************ 用途说明:给用户分配角色权限* @param userId* @param roleIds* 返回值说明:* @return boolean***********************************/@Overridepublic boolean saveUserRelationShip(String userId, String[] roleIds) {//删除旧的所有角色权限userRoleService.remove(new QueryWrapper().eq("user_id", userId));List list = new ArrayList<>();for (String id : roleIds) {UserRole rolePermission = new UserRole();rolePermission.setRoleId(id);rolePermission.setUserId(userId);list.add(rolePermission);}return userRoleService.saveBatch(list);}/************************************ 用途说明:根据userid获取用户信息* @param userId* 返回值说明:* @return java.util.List***********************************/@Overridepublic List selectRoleByUserId(String userId) {//根据用户id获取角色列表List userRoleList = userRoleService.list(new QueryWrapper().eq("user_id", userId).select("role_id"));//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记List roleIdLists = userRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList());List roleList = new ArrayList<>();if (roleIdLists.size() > 0) {roleList = baseMapper.selectBatchIds(roleIdLists);}return roleList;}
}
package com.stu.service.acl.service;import com.alibaba.fastjson.JSONObject;import java.util.List;
import java.util.Map;public interface IndexService {/************************************ 用途说明:根据用户明获取用户登录信息* @param userName* 返回值说明:* @return java.util.Map***********************************/Map getUserInfo(String userName);/************************************ 用途说明:根据用户动态获取菜单* @param userName* 返回值说明:* @return java.util.List***********************************/List getMenu(String userName);
}
package com.stu.service.acl.service.impl;import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.entity.Role;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.service.IndexService;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.RoleService;
import com.stu.service.acl.service.UserService;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 22:34******************************/
@Service
public class IndexServiceImpl implements IndexService {@Autowiredprivate UserService userService;@Autowiredprivate RoleService roleService;@Autowiredprivate PermissionService permissionService;@Autowiredprivate RedisTemplate redisTemplate;/************************************ 用途说明:根据用户明获取用户登录信息* @param userName* 返回值说明:* @return java.util.Map***********************************/@Overridepublic Map getUserInfo(String userName) {Map result = new HashMap<>();User user = userService.selectByUserName(userName);if (user == null) {throw new CustomException(ResultCodeEnum.FETCH_USERINFO_ERROR);}//根据用户id获取角色List roleList = roleService.selectRoleByUserId(user.getId());//转换成角色名称列表List roleNameList = roleList.stream().map(item -> item.getRoleName()).collect(Collectors.toList());//前端框架必须返回一个角色,否则报错,如果没有角色,返回一个空角色if (roleNameList.size() == 0) {roleNameList.add("");}List permissionValueList = permissionService.selectPermissionValueListByUserId(user.getId());redisTemplate.opsForValue().set(userName, permissionValueList);result.put("name", user.getUsername());result.put("roles", roleNameList);result.put("permissionValueList", permissionValueList);return result;}/************************************ 用途说明:根据用户动态获取菜单* @param userName* 返回值说明:* @return java.util.List***********************************/@Overridepublic List getMenu(String userName) {User user = userService.selectByUserName(userName);if (user == null) {throw new CustomException(ResultCodeEnum.FETCH_USERINFO_ERROR);}//根据用户动态获取菜单return permissionService.selectPermissionByUserId(user.getId());}
}
package com.stu.service.acl.controller.admin;import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.service.IndexService;
import com.stu.service.base.result.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Map;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 21:42******************************/
@RestController
@RequestMapping("/admin/acl/index")
public class IndexController {@Autowiredprivate IndexService indexService;/************************************ 用途说明:退出* 返回值说明:* @return com.stu.service.base.result.R***********************************/@PostMapping("logout")public R logout() {return R.ok();}/************************************ 用途说明:* 返回值说明:* @return com.stu.service.base.result.R***********************************/@GetMapping("getUserInfo")public R getUserInfo() {String userName = SecurityContextHolder.getContext().getAuthentication().getName();Map userInfo = indexService.getUserInfo(userName);return R.ok().data(userInfo);}/************************************ 用途说明:根据用户明获取动态菜单* 返回值说明:* @return com.stu.service.base.result.R***********************************/@GetMapping("menu")public R menu() {String userName = SecurityContextHolder.getContext().getAuthentication().getName();List permissionList = indexService.getMenu(userName);return R.ok().data("permissionList", permissionList);}
}
6、Spring Security配置
(1)核心配置类:TokenWebSecurityConfig
Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。
这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置。
package com.stu.security.config;import com.stu.security.filter.TokenAuthenticationFilter;
import com.stu.security.filter.TokenLoginFilter;
import com.stu.security.security.DefaultPasswordEncoder;
import com.stu.security.security.TokenLogoutHandler;
import com.stu.security.security.TokenManager;
import com.stu.security.security.UnauthorizedEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:30******************************/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate DefaultPasswordEncoder defaultPasswordEncoder;@Autowiredprivate TokenManager tokenManager;@Autowiredprivate RedisTemplate redisTemplate;/************************************ 用途说明:密码处理* @param auth* 返回值说明:***********************************/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}/************************************ 用途说明:配置设置* @param http* 返回值说明:***********************************/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint()) //未授权异常处理.and().csrf().disable().authorizeRequests().anyRequest().authenticated() //取消跨域所有请求.and().logout().logoutUrl("/admin/acl/index/logout").addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();}/************************************ 用途说明:配置哪些请求不拦截* @param web* 返回值说明:***********************************/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**","/swagger-resources/**","/webjars/**","/v2/**","/swagger-ui.html/**");}
}
package com.stu.security.entity;import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.io.Serializable;
import java.util.Date;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:38******************************/
@Data
@ApiModel(value = "User对象", description = "用户表")
public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "会员id")private String id;@ApiModelProperty(value = "用户名称")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "昵称")private String nickName;@ApiModelProperty(value = "用户头像")private String salt;@ApiModelProperty(value = "用户签名")private String token;}
package com.stu.security.entity;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:38******************************/
@Data
@Slf4j
public class SecurityUser implements UserDetails {//当前登录用户private transient User currentUserInfo;//当前登录用户权限private List permissionValueList;public SecurityUser() {}public SecurityUser(User user) {if (user != null) {this.currentUserInfo = user;}}/************************************ 用途说明:获取当前用户的所有权限* 返回值说明:* @return java.util.Collection extends org.springframework.security.core.GrantedAuthority>***********************************/@Overridepublic Collection extends GrantedAuthority> getAuthorities() {Collection authorities = new ArrayList<>();for(String permissionValue:permissionValueList){if(StringUtils.isEmpty(permissionValue)){continue;}SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);authorities.add(authority);}return authorities;}@Overridepublic String getPassword() {return currentUserInfo.getPassword();}@Overridepublic String getUsername() {return currentUserInfo.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
package com.stu.security.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.security.entity.SecurityUser;
import com.stu.security.entity.User;
import com.stu.security.security.TokenManager;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.R;
import com.stu.service.base.result.ResultCodeEnum;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;/******************************* 用途说明:登录过滤器,对用户名称和密码进行校验* 作者姓名: Administrator* 创建时间: 2022-09-01 9:39******************************/
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private AuthenticationManager authenticationManager;private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLoginFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;//设置访问方式,可以post以为的访问this.setPostOnly(false);this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login", "POST"));}/************************************ 用途说明:验证账号密码是否正确* @param request* @param response* 返回值说明:* @return org.springframework.security.core.Authentication***********************************/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {try {User user = new ObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));} catch (Exception e) {e.printStackTrace();throw new CustomException(ResultCodeEnum.LOGIN_MOBILE_ERROR);}}/************************************ 用途说明:登录成功* @param request* @param response* @param chain* @param authResult* 返回值说明:***********************************/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException, ServletException {SecurityUser user = (SecurityUser) authResult.getPrincipal();String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());ResponseUtil.out(response, R.ok().data("token", token));}/************************************ 用途说明:登录失败* @param request* @param response* @param e* 返回值说明:***********************************/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}
package com.stu.security.filter;import com.stu.security.security.TokenManager;
import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import io.netty.util.internal.StringUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:39******************************/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {private AuthenticationManager authenticationManager;private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthenticationFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {super(authenticationManager);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException, ServletException {if (request.getRequestURI().indexOf("admin") == -1) {chain.doFilter(request, response);return;}UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = null;try {usernamePasswordAuthenticationToken = getAuthentication(request);} catch (Exception e) {ResponseUtil.out(response, R.error());}if (usernamePasswordAuthenticationToken != null) {SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);} else {ResponseUtil.out(response, R.error());}chain.doFilter(request, response);}/************************************ 用途说明:从request获取token,根据token获取权限列表* 返回值说明:* @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken***********************************/private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {String token = request.getHeader("token");if (token != null && !"".equals(token.trim())) {String userName = tokenManager.getUserFromToken(token);List permissionValueList = (List) redisTemplate.opsForValue().get(userName);Collection authorities = new ArrayList<>();for (String permissionValue : permissionValueList) {if (StringUtils.isEmpty(permissionValue)) {continue;}SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permissionValue);authorities.add(simpleGrantedAuthority);}return new UsernamePasswordAuthenticationToken(userName, token, authorities);}return null;}
}
package com.stu.security.security;import com.stu.service.base.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:39******************************/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {/*** 加密密码** @param rawPassword*/@Overridepublic String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}/*** 判断密码是否正确** @param rawPassword the raw password to encode and match* @param encodedPassword the encoded password from storage to compare with* @return true if the raw password, after encoding, matches the encoded password from* storage*/@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}
}
package com.stu.security.security;import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:39******************************/
public class TokenLogoutHandler implements LogoutHandler {private AuthenticationManager authenticationManager;private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request,HttpServletResponse response,Authentication authentication) {String token = request.getHeader("token");if (token != null) {tokenManager.removeToken(token);String userName = tokenManager.getUserFromToken(token);redisTemplate.delete(userName);ResponseUtil.out(response, R.ok());}}
}
package com.stu.security.security;import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import java.util.Date;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:40******************************/
@Component
public class TokenManager {private long tokenExpiration = 24*60*60*1000;private String tokenSignKey = "123456";public String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}public String getUserFromToken(String token) {String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return user;}public void removeToken(String token) {//jwttoken无需删除,客户端扔掉即可。}
}
package com.stu.security.security;import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/******************************* 用途说明:* 作者姓名: Administrator* 创建时间: 2022-09-01 9:40******************************/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(httpServletResponse, R.error());}
}
目录截图

作者:明
出处:https://www.cnblogs.com/konglxblog//
版权:本文版权归作者和博客园共有
转载:欢迎转载,文章中请给出原文连接,此文章仅为个人知识学习分享,否则必究法律责任
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
