一、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
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>common</artifactId> <groupId>com.javaclimb</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring_security</artifactId> <dependencies> <dependency> <groupId>com.javaclimb</groupId> <artifactId>common_util</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.javaclimb</groupId> <artifactId>service_base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- Spring Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> </dependencies> </project>
<dependency> <groupId>com.javaclimb</groupId> <artifactId>spring_security</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
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; /** * <p> * 用户表 服务类 * </p> * * @author stu * @since 2022-08-16 */ public interface UserService extends IService<User> { /*********************************** * 用途说明:获取用户详情 * @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; /** * <p> * 用户表 服务实现类 * </p> * * @author stu * @since 2022-08-16 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { /*********************************** * 用途说明:获取用户详情 * @param username * 返回值说明: * @return com.stu.service.acl.entity.User ***********************************/ @Override public User selectByUserName(String username) { return baseMapper.selectOne(new QueryWrapper<User>().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; /** * <p> * 权限 Mapper 接口 * </p> * * @author stu * @since 2022-08-16 */ public interface PermissionMapper extends BaseMapper<Permission> { /*********************************** * 用途说明:获取全部菜单的权限 * 返回值说明: * @return java.util.List<java.lang.String> ***********************************/ List<String> selectAllPermissionValue(); /*********************************** * 用途说明:根据用户id查询所有权限 * @param id * 返回值说明: * @return java.util.List<java.lang.String> ***********************************/ List<String> selectPermissionValueByUserId(String id); /*********************************** * 用途说明:根据用户id查询所有菜单 * @param userId * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ List<Permission> selectPermissionByUserId(String userId); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.stu.service.acl.mapper.PermissionMapper"> <resultMap id="permissionMap" type="com.stu.service.acl.entity.Permission"> <result property="id" column="id"/> <result property="pid" column="pid"/> <result property="name" column="name"/> <result property="type" column="type"/> <result property="permissionValue" column="permission_value"/> <result property="path" column="path"/> <result property="component" column="component"/> <result property="icon" column="icon"/> <result property="status" column="status"/> <result property="isDeleted" column="is_deleted"/> <result property="gmtCreate" column="gmt_create"/> <result property="gmtModified" column="gmt_modified"/> </resultMap> <!-- 用于select查询公用抽取的列 --> <sql id="columns"> 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 </sql> <select id="selectPermissionByUserId" resultMap="permissionMap"> select <include refid="columns" /> from acl_user_role ur inner join acl_role_permission rp on rp.role_id = ur.role_id inner join acl_permission p on p.id = rp.permission_id where ur.user_id = #{userId} and ur.is_deleted = 0 and rp.is_deleted = 0 and p.is_deleted = 0 </select> <select id="selectPermissionValueByUserId" resultType="String"> select p.permission_value from acl_user_role ur inner join acl_role_permission rp on rp.role_id = ur.role_id inner join acl_permission p on p.id = rp.permission_id where ur.user_id = #{userId} and ur.is_deleted = 0 and rp.is_deleted = 0 and p.is_deleted = 0 </select> <select id="selectAllPermissionValue" resultType="String"> select permission_value from acl_permission where is_deleted = 0 </select> </mapper>
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; /** * <p> * 权限 服务类 * </p> * * @author stu * @since 2022-08-16 */ public interface PermissionService extends IService<Permission> { /*********************************** * 用途说明:查询所有权限菜单 * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ List<Permission> ListAllPermissions(); /*********************************** * 用途说明:递归删除菜单 * @param id * 返回值说明: * @return boolean ***********************************/ boolean removeChildById(String id); /*********************************** * 用途说明:根據角色獲取菜單 * @param id * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ List<Permission> listAllMenu(String id); /*********************************** * 用途说明:给角色分配菜单权限 * @param roleId * @param permissionId * 返回值说明: * @return boolean ***********************************/ boolean saveRolePermissionrelationShip(String roleId, String[] permissionId); /*********************************** * 用途说明:根据用户id查询有权限的菜单 * @param id * 返回值说明: * @return java.util.List<java.lang.String> ***********************************/ List<String> selectPermissionValueListByUserId(String id); /*********************************** * 用途说明:根据用户id查询所有权限的菜单详细列表 * @param userId * 返回值说明: * @return java.util.List<org.json.JSONObject> ***********************************/ List<JSONObject> 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; /** * <p> * 权限 服务实现类 * </p> * * @author stu * @since 2022-08-16 */ @Service public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService { @Autowired private RolePermissionService rolePermissionService; @Autowired private UserService userService; /*********************************** * 用途说明:查询所有权限菜单 * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ @Override public List<Permission> ListAllPermissions() { QueryWrapper<Permission> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("id"); return bulidPermission(baseMapper.selectList(queryWrapper)); } /*********************************** * 用途说明:把返回所有菜单list集合进行封装的方法 * @param list * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ private List<Permission> bulidPermission(List<Permission> list) { //创建list集合,用于数据最终封装 List<Permission> finalNode = new ArrayList<>(); //把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1 for (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<Permission> list) { //1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化 permission.setChildren(new ArrayList<Permission>()); //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 ***********************************/ @Override public boolean removeChildById(String id) { List<String> idList = new ArrayList<>(); selectChildListById(id, idList); idList.add(id); return baseMapper.deleteBatchIds(idList) > 0; } /*********************************** * 用途说明:根據角色獲取菜單 * @param id * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ @Override public List<Permission> listAllMenu(String id) { //获取所有菜单 List<Permission> allPermissionList = baseMapper.selectList(new QueryWrapper<>()); //根据角色id呼气角色权限列表 List<RolePermission> rolePermissionsList = rolePermissionService .list(new QueryWrapper<RolePermission>().eq("role_id", id)); //遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记 List<String> 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 ***********************************/ @Override public boolean saveRolePermissionrelationShip(String roleId, String[] permissionId) { //删除旧的权限 rolePermissionService.remove(new QueryWrapper<RolePermission>().eq("role_id", roleId)); List<RolePermission> 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<java.lang.String> ***********************************/ @Override public List<String> selectPermissionValueListByUserId(String id) { List<String> list; if (checkAdmin(id)) { //如果是超级管理员获取所有权限 list = baseMapper.selectAllPermissionValue(); } else { //根据用户id查询所有权限 list = baseMapper.selectPermissionValueByUserId(id); } return list; } /*********************************** * 用途说明:根据用户id查询所有权限的菜单详细列表 * @param userId * 返回值说明: * @return java.util.List<org.json.JSONObject> ***********************************/ @Override public List<JSONObject> selectPermissionByUserId(String userId) { List<Permission> selectPermissionList = null; if (checkAdmin(userId)) { //如果是超级管理员获取所有权限 selectPermissionList = baseMapper.selectList(null); } else { //根据用户id查询所有权限 selectPermissionList = baseMapper.selectPermissionByUserId(userId); } //先转换成树状 List<Permission> permissionList = bulidPermission(selectPermissionList); //然后转化成前端需要的格式 List<JSONObject> result = bulidJson(permissionList); return result; } /*********************************** * 用途说明:转化成前端需要的格式 * @param permissionList * 返回值说明: * @return java.util.List<org.json.JSONObject> ***********************************/ private List<JSONObject> bulidJson(List<Permission> permissionList) { List<JSONObject> menus = new ArrayList<>(); if (permissionList.size() == 1) { Permission topNode = permissionList.get(0); //组建左侧一级菜单 List<Permission> 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<JSONObject> children = new ArrayList<>(); List<Permission> 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<Permission> 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<String> idList) { //查询当前菜单的下级 QueryWrapper<Permission> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("pid", id); queryWrapper.select("id"); List<Permission> 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 { @Autowired private UserService userService; @Autowired private PermissionService permissionService; /** *根据用户名查询用户信息 * @param username the username identifying the user whose data is required. * @return a fully populated user record (never <code>null</code>) * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ @Override public 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<String> 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; /** * <p> * 服务类 * </p> * * @author stu * @since 2022-08-16 */ public interface RoleService extends IService<Role> { /*********************************** * 用途说明:根据用户获取角色 * @param userId * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ Map<String, Object> findRoleByUserId(String userId); /*********************************** * 用途说明:给用户分配角色权限 * @param userId * @param permissionIds * 返回值说明: * @return boolean ***********************************/ boolean saveUserRelationShip(String userId, String[] permissionIds); /*********************************** * 用途说明:根据userid获取用户信息 * @param id * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Role> ***********************************/ List<Role> 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; /** * <p> * 服务实现类 * </p> * * @author stu * @since 2022-08-16 */ @Service public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService { @Autowired private UserRoleService userRoleService; /*********************************** * 用途说明:根据用户获取角色 * @param userId * 返回值说明: * @return java.util.List<com.stu.service.acl.entity.Permission> ***********************************/ @Override public Map<String, Object> findRoleByUserId(String userId) { //获取所有角色 List<Role> allRoleList = baseMapper.selectList(new QueryWrapper<>()); //根据用户id获取角色列表 List<UserRole> existUserRoleList = userRoleService .list(new QueryWrapper<UserRole>().eq("user_id", userId).select("role_id")); //遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记 List<String> existRoleLists = existUserRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList()); List<Role> assignRoles = new ArrayList<>(); allRoleList.forEach(role -> { if (existRoleLists.contains(role.getId())) { assignRoles.add(role); } }); Map<String, Object> roleMap = new HashMap<>(); roleMap.put("assignRoles", assignRoles); roleMap.put("allRoleList", allRoleList); return roleMap; } /*********************************** * 用途说明:给用户分配角色权限 * @param userId * @param roleIds * 返回值说明: * @return boolean ***********************************/ @Override public boolean saveUserRelationShip(String userId, String[] roleIds) { //删除旧的所有角色权限 userRoleService.remove(new QueryWrapper<UserRole>().eq("user_id", userId)); List<UserRole> 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<com.stu.service.acl.entity.Role> ***********************************/ @Override public List<Role> selectRoleByUserId(String userId) { //根据用户id获取角色列表 List<UserRole> userRoleList = userRoleService .list(new QueryWrapper<UserRole>().eq("user_id", userId).select("role_id")); //遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记 List<String> roleIdLists = userRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList()); List<Role> 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<java.lang.String, java.lang.Object> ***********************************/ Map<String, Object> getUserInfo(String userName); /*********************************** * 用途说明:根据用户动态获取菜单 * @param userName * 返回值说明: * @return java.util.List<org.json.JSONObject> ***********************************/ List<JSONObject> 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 { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; @Autowired private RedisTemplate redisTemplate; /*********************************** * 用途说明:根据用户明获取用户登录信息 * @param userName * 返回值说明: * @return java.util.Map<java.lang.String, java.lang.Object> ***********************************/ @Override public Map<String, Object> getUserInfo(String userName) { Map<String, Object> result = new HashMap<>(); User user = userService.selectByUserName(userName); if (user == null) { throw new CustomException(ResultCodeEnum.FETCH_USERINFO_ERROR); } //根据用户id获取角色 List<Role> roleList = roleService.selectRoleByUserId(user.getId()); //转换成角色名称列表 List<String> roleNameList = roleList.stream() .map(item -> item.getRoleName()).collect(Collectors.toList()); //前端框架必须返回一个角色,否则报错,如果没有角色,返回一个空角色 if (roleNameList.size() == 0) { roleNameList.add(""); } List<String> 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<org.json.JSONObject> ***********************************/ @Override public List<JSONObject> 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 { @Autowired private 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<String, Object> 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<JSONObject> 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 { @Autowired private UserDetailsService userDetailsService; @Autowired private DefaultPasswordEncoder defaultPasswordEncoder; @Autowired private TokenManager tokenManager; @Autowired private RedisTemplate redisTemplate; /*********************************** * 用途说明:密码处理 * @param auth * 返回值说明: ***********************************/ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder); } /*********************************** * 用途说明:配置设置 * @param http * 返回值说明: ***********************************/ @Override protected 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 * 返回值说明: ***********************************/ @Override public 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<String> permissionValueList; public SecurityUser() { } public SecurityUser(User user) { if (user != null) { this.currentUserInfo = user; } } /*********************************** * 用途说明:获取当前用户的所有权限 * 返回值说明: * @return java.util.Collection<? extends org.springframework.security.core.GrantedAuthority> ***********************************/ @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<>(); for(String permissionValue:permissionValueList){ if(StringUtils.isEmpty(permissionValue)){ continue; } SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue); authorities.add(authority); } return authorities; } @Override public String getPassword() { return currentUserInfo.getPassword(); } @Override public String getUsername() { return currentUserInfo.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public 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 ***********************************/ @Override public 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 * 返回值说明: ***********************************/ @Override protected 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 * 返回值说明: ***********************************/ @Override protected 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; } @Override protected 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<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName); Collection<GrantedAuthority> 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 */ @Override public 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 */ @Override public 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; } @Override public 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 { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { ResponseUtil.out(httpServletResponse, R.error()); } }
目录截图
作者:明
出处:https://www.cnblogs.com/konglxblog//
版权:本文版权归作者和博客园共有
转载:欢迎转载,文章中请给出原文连接,此文章仅为个人知识学习分享,否则必究法律责任