Spring Boot 整合 Spring Security 示例实现前后分离权限注解 + JWT 登录认证
Spring Boot 整合 Spring Security 示例实现前后分离权限注解 + JWT 登录认证
1:建表:用户、角色、资源
CREATE TABLE `user` ( `id` int(64) NOT NULL AUTO_INCREMENT, `user_name` varchar(32) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enable` tinyint(4) DEFAULT '0', `locked` tinyint(4) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `role_resource` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL, `resource_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 CREATE TABLE `resources` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pattern` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL COMMENT '资源名字', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `resources` VALUES (1, 'getUserInfo','查看用户信息'); INSERT INTO `resources` VALUES (2, 'getResurcesInfo', '查看所有权限'); INSERT INTO `resources` VALUES (3, 'getRoleInfo', '查看所有角色'); INSERT INTO `role` VALUES (1, 'ADMIN','管理员'); INSERT INTO `role` VALUES (2, 'USER', '普通用户'); INSERT INTO `role_resource` VALUES (1, 1, 1); INSERT INTO `role_resource` VALUES (2, 1, 2); INSERT INTO `role_resource` VALUES (3, 1, 3); INSERT INTO `role_resource` VALUES (4, 2, 1); INSERT INTO `user`(id, user_name, `password`) VALUES (1, 'admin', '123456'); INSERT INTO `user`(id, user_name, `password`) VALUES (2, 'user', '123456'); INSERT INTO `user_role` VALUES (1, 1, 1); INSERT INTO `user_role` VALUES (2, 2, 2);
2、Maven依赖如下:
<!-- StringUtilS工具 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency> <!-- JSON工具 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency> <!-- JWT依赖 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.0.9.RELEASE</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--Security依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Druid 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> <!-- region MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency>
3、yml配置如下:
server: port: 8848 mybatis: mapper-locations: classpath*:mapper/*Mapper.xml configuration: database-id: MySQL # 开启驼峰转换 map-underscore-to-camel-case: true # spring boot集成mybatis的方式打印sql # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl spring: application: name: security-jwt-study datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false username: root password: 123456 filters: stat # 设置最大数据库连接数,设为0为无限制 maxActive: 20 # 配置初始化大小、最小、最大 initialSize: 1 # 最大等待时间 maxWait: 60000 # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值 minIdle: 1 timeBetweenEvictionRunsMillis: 6000 # 连接在池中保持空闲而不被回收的最小时间(毫秒) minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' # 对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false) testWhileIdle: true # 当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接 testOnBorrow: true # 当一个连接使用完归还到连接池时是否进行验证 testOnReturn: false # 启用游标缓存,这个对数据库的性能提升很大 poolPreparedStatements: true # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 maxOpenPreparedStatements: 20 filter: stat: log-slow-sql: true slow-sql-millis: 2000
4、编写项目基础类
Entity,Dao,Service,及等SpringSecurity用户的Entity,Service类等在这里省略,请参考源码
编写JWT工具类
package com.dw.study.jwt; import com.alibaba.fastjson.JSON; import com.dw.study.entity.MyUser; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; import javax.crypto.SecretKey; import java.util.Date; /** * JWT工具类 * * @Author Sans * @CreateTime 2019/10/2 7:42 */ @Slf4j public class JWTTokenUtil { // JWT的过期时间 public static final long JWT_EXPIRE = 60 * 60 * 24*1000; //过期时间,毫秒,一天 // JWT的生成密钥 public static final String JWT_SECURITY = "JWTSecret"; // #请求的 Header KEY public static final String HEAD_KEY = "Authorization"; // 生成的Token的前缀: public static final String TOKEN_PREFIX = "Sans-"; /** * 生成Token * * @Author Sans * @CreateTime 2019/10/2 12:16 * @Param selfUserEntity 用户安全实体 * @Return Token */ public String createAccessToken(MyUser selfUserEntity) { // 登陆成功生成JWT String token = Jwts.builder() //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(selfUserEntity.getId() + "") // 主题(代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。) .setSubject(selfUserEntity.getUsername()) // 签发时间 .setIssuedAt(new Date()) // 签发者 .setIssuer("sans") // 自定义属性 放入用户拥有权限 .claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities())) // 失效时间 .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRE)) // 设置签名使用的签名算法和签名使用的秘钥 //生成签名的时候使用的秘钥secret,这个方法在本地封装,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。 // 它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 .signWith(SignatureAlgorithm.HS512, JWT_SECURITY) //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt .compact(); return token; } /** * @return * @Author dw * @Description 解析JWT-Token * @Date 2020/5/14 16:31 * @Param */ public static Claims parseJWT(String jwt) throws Exception { Claims claims = Jwts.parser() //得到DefaultJwtParser //设置签名的秘钥 .setSigningKey(JWT_SECURITY) //设置需要解析的jwt .parseClaimsJws(jwt).getBody(); return claims; } /** * 获取token的过期时间 * * @param token token * @return 过期时间 */ public Date getExpirationDateFromToken(String token) throws Exception { return parseJWT(token) .getExpiration(); } /** * 判断token是否过期 * * @param token token * @return 已过期返回true,未过期返回false */ private Boolean isTokenExpired(String token) throws Exception { Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } }
编写暂无权限处理类
/** * @Description 没有权限时 的处理类 * @Author Sans * @CreateTime 2019/10/3 8:39 */ @Component public class UserAuthAccessDeniedHandler implements AccessDeniedHandler { /** * 暂无权限返回结果 * @Author Sans * @CreateTime 2019/10/3 8:41 */ @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){ ResultUtil.responseJson(response,ResultUtil.resultCode(403,"暂无权限访问,请管理员授权")); } }
编写用户未登录处理类
/** * 用户未登录处理类 * @Author Sans * @CreateTime 2019/10/3 8:55 */ @Component public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint { /** * 用户未登录返回结果 * @Author Sans * @CreateTime 2019/10/3 9:01 */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录")); } }
编写登录失败处理类
@Slf4j @Component public class UserLoginFailureHandler implements AuthenticationFailureHandler { /** * 登录失败返回结果 * @Author Sans * @CreateTime 2019/10/3 9:12 */ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ // 这些对于操作的处理类可以根据不同异常进行不同处理 if (exception instanceof UsernameNotFoundException){ log.info("【登录失败】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在")); }else if (exception instanceof LockedException){ log.info("【登录失败】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结")); } else if (exception instanceof BadCredentialsException){ log.info("【登录失败】"+exception.getMessage()); ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名密码不正确")); } else { ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败,用户账号异常")); } } }
编写登录成功处理类
@Slf4j @Component public class UserLoginSuccessHandler implements AuthenticationSuccessHandler { /** * 登录成功返回结果 * @Author Sans * @CreateTime 2019/10/3 9:27 */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){ // 组装JWT MyUser selfUserEntity = (MyUser) authentication.getPrincipal(); JWTTokenUtil jwtTokenUtil = new JWTTokenUtil(); String token = jwtTokenUtil.createAccessToken(selfUserEntity); token = JWTTokenUtil.TOKEN_PREFIX + token; // 封装返回参数 Map<String,Object> resultData = new HashMap<>(); resultData.put("code","200"); resultData.put("msg", "登录成功"); resultData.put("token",token); ResultUtil.responseJson(response,resultData); } }
编写登出成功处理类
/** * 登出成功处理类 * @Author Sans * @CreateTime 2019/10/3 9:42 */ @Component public class UserLogoutSuccessHandler implements LogoutSuccessHandler { /** * 用户登出返回结果 * 这里应该让前端清除掉Token * @Author Sans * @CreateTime 2019/10/3 9:50 */ @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){ Map<String,Object> resultData = new HashMap<>(); resultData.put("code","200"); resultData.put("msg", "登出成功"); SecurityContextHolder.clearContext(); ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData)); } }
五、编写Security核心类
编写自定义登录验证类
/** * 自定义登录验证 * @Author Sans * @CreateTime 2019/10/1 19:11 */ @Component public class UserAuthenticationProvider implements AuthenticationProvider { @Autowired private UserMapperDao userMapperDao; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 获取表单输入中返回的用户名 String userName = (String) authentication.getPrincipal(); // 获取表单中输入的密码 String password = (String) authentication.getCredentials(); // 查询用户是否存在 MyUser userDetails = userMapperDao.loadUserByUsername(userName); if (userDetails == null) { throw new UsernameNotFoundException("用户名不存在"); } // 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的 if (!bCryptPasswordEncoder.matches(password, bCryptPasswordEncoder.encode(userDetails.getPassword()))) { throw new BadCredentialsException("密码不正确"); } // 还可以加一些其他信息的判断,比如用户账号已停用等判断 if (!userDetails.isAccountNonLocked()){ throw new LockedException("该用户已被冻结"); } // 查询用户角色 List<Role> userRoles = userMapperDao.getUserRolesByUid(userDetails.getId()); // ROLE_ 是springsecurity对于角色的默认前缀,如果不加,验证会失败,数据库需要添加前缀 // 角色集合 HashSet<GrantedAuthority> authorities = new HashSet<>(); // ROLE_ 是springsecurity对于角色的默认前缀,如果不加,验证会失败(或者在数据库中,添加Role时添加前缀) for (Role role: userRoles){ authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); } userDetails.setAuthorities(authorities); // 进行登录 return new UsernamePasswordAuthenticationToken(userDetails, password); } @Override public boolean supports(Class<?> authentication) { return true; } }
编写自定义PermissionEvaluator注解验证
** * 自定义权限注解验证 * @Author Sans * @CreateTime 2019/10/6 13:31 */ @Component public class UserPermissionEvaluator implements PermissionEvaluator { @Autowired private ResourceMapperDao resourceMapperDao; /** * hasPermission鉴权方法 * 这里仅仅判断PreAuthorize注解中的权限表达式 * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权 * 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计 * @Author Sans * @CreateTime 2019/10/6 18:25 * @Param authentication 用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上) * @Param targetUrl 请求路径 * @Param permission 请求路径权限 * @Return boolean 是否通过 */ @Override public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) { // 获取用户信息 MyUser selfUserEntity =(MyUser) authentication.getPrincipal(); // 查询用户权限(这里可以将权限放入缓存中提升效率) Set<String> permissions = new HashSet<>(); List<Resources> userResources = resourceMapperDao.queryResourcesByUserId(selfUserEntity.getId()); for (Resources resources: userResources) { permissions.add(resources.getPattern()); } // 权限对比 if (permissions.contains(permission.toString())){ return true; } return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; } }
编写SpringSecurity核心配置类
/** * SpringSecurity配置类 * @Author Sans * @CreateTime 2019/10/1 9:40 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的 public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 自定义登录成功处理器 */ @Autowired private UserLoginSuccessHandler userLoginSuccessHandler; /** * 自定义登录失败处理器 */ @Autowired private UserLoginFailureHandler userLoginFailureHandler; /** * 自定义注销成功处理器 */ @Autowired private UserLogoutSuccessHandler userLogoutSuccessHandler; /** * 自定义暂无权限处理器 */ @Autowired private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler; /** * 自定义未登录的处理器 */ @Autowired private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler; /** * 自定义登录逻辑验证器 */ @Autowired private UserAuthenticationProvider userAuthenticationProvider; /** * @Author dw * @Description 如果不使用上面的自定义登录逻辑,就走默认的认证 * @Date 2020/5/14 17:46 * @Param * @return */ // @Autowired // public MyUserDetailServiceImpl myUserDetailService; /** * 加密方式 * @Author Sans * @CreateTime 2019/10/1 14:00 */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } /** * 注入自定义PermissionEvaluator */ @Bean public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){ DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler(); handler.setPermissionEvaluator(new UserPermissionEvaluator()); return handler; } /** * 配置登录验证逻辑 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // security 默认的认证逻辑 // auth.userDetailsService(myUserDetailService); //这里可启用我们自己的登陆验证逻辑 auth.authenticationProvider(userAuthenticationProvider); } /** * 配置security的控制逻辑 * @Author Sans * @CreateTime 2019/10/1 16:56 * @Param http 请求 */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 不进行权限验证的请求或资源 .antMatchers("/login/*","/index").permitAll() // 其他的需要登陆后才能访问 .anyRequest().authenticated() .and() // 配置未登录自定义处理类 .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler) .and() // 配置登录地址 .formLogin() .loginProcessingUrl("/login/userLogin") // 配置登录成功自定义处理类 .successHandler(userLoginSuccessHandler) // 配置登录失败自定义处理类 .failureHandler(userLoginFailureHandler) .and() // 配置登出地址 .logout() .logoutUrl("/login/userLogout") // 配置用户登出自定义处理类 .logoutSuccessHandler(userLogoutSuccessHandler) .and() // 配置没有权限自定义处理类 .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler) .and() // 开启跨域 .cors() .and() // 取消跨站请求伪造防护 .csrf().disable(); // 基于Token不需要session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用缓存 http.headers().cacheControl(); // 添加JWT过滤器 http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager())); } }
6、编写JWT拦截类
编写JWT接口请求校验拦截器
/** * JWT接口请求校验拦截器 * 请求接口时会进入这里验证Token是否合法和过期,如果令牌合法则获取用户信息,并且存入SecurityContextHolder * @Author Sans * @CreateTime 2019/10/5 16:41 */ @Slf4j public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter { public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取请求头中JWT的Token String tokenHeader = request.getHeader(JWTTokenUtil.HEAD_KEY); if (null!=tokenHeader && tokenHeader.startsWith(JWTTokenUtil.TOKEN_PREFIX)) { try { // 截取JWT前缀 String token = tokenHeader.replace(JWTTokenUtil.TOKEN_PREFIX, ""); // 解析JWT Claims claims = JWTTokenUtil.parseJWT(token); // 获取用户名 String username = claims.getSubject(); String userId=claims.getId(); if(StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(userId)) { // 获取角色 List<GrantedAuthority> authorities = new ArrayList<>(); String authority = claims.get("authorities").toString(); if(!StringUtils.isEmpty(authority)){ List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class); for(Map<String,String> role : authorityMap){ if(null != role) { authorities.add(new SimpleGrantedAuthority(role.get("authority"))); } } } //组装参数 MyUser selfUserEntity = new MyUser(); selfUserEntity.setUserName(claims.getSubject()); selfUserEntity.setId(claims.getId()); selfUserEntity.setAuthorities(authorities); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (ExpiredJwtException e){ log.info("Token过期"); } catch (Exception e) { log.info("Token无效"); } } filterChain.doFilter(request, response); } }
7、权限注解和hasPermission权限扩展
Security允许我们在定义URL方法访问所应有的注解权限时使用SpringEL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则没有权限,会进入到我们配置的UserAuthAccessDeniedHandler(暂无权限处理类)中进行处理
eg:
/** * 管理端信息 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回数据MAP */ @PreAuthorize("hasRole('ADMIN')") @RequestMapping(value = "/info",method = RequestMethod.GET) public Map<String,Object> userLogin(){ Map<String,Object> result = new HashMap<>(); SelfUserEntity userDetails = SecurityUtil.getUserInfo(); result.put("title","管理端信息"); result.put("data",userDetails); return ResultUtil.resultSuccess(result); } /** * 拥有ADMIN或者USER角色可以访问 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回数据MAP */ @PreAuthorize("hasAnyRole('ADMIN','USER')") @RequestMapping(value = "/list",method = RequestMethod.GET) public Map<String,Object> list(){ Map<String,Object> result = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); result.put("title","拥有用户或者管理员角色都可以查看"); result.put("data",sysUserEntityList); return ResultUtil.resultSuccess(result); } /** * 拥有ADMIN和USER角色可以访问 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回数据MAP */ @PreAuthorize("hasRole('ADMIN') and hasRole('USER')") @RequestMapping(value = "/menuList",method = RequestMethod.GET) public Map<String,Object> menuList(){ Map<String,Object> result = new HashMap<>(); List<SysMenuEntity> sysMenuEntityList = sysMenuService.list(); result.put("title","拥有用户和管理员角色都可以查看"); result.put("data",sysMenuEntityList); return ResultUtil.resultSuccess(result); }
通常情况下使用hasRole和hasAnyRole基本可以满足大部分鉴权需求,但是有时候面对更复杂的场景上述常规表示式无法完成权限认证,Security也为我们提供了解决方案.通过hasPermission()来扩展表达式.使用hasPermission()首先要实现PermissionEvaluator接口(参考上面的自定义权限验证)
在请求方法上添加hasPermission示例:
/** * 拥有sys:user:info权限可以访问 * hasPermission 第一个参数是请求路径 第二个参数是权限表达式 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回数据MAP */ @PreAuthorize("hasPermission('/admin/userList','sys:user:info')") @RequestMapping(value = "/userList",method = RequestMethod.GET) public Map<String,Object> userList(){ Map<String,Object> result = new HashMap<>(); List<SysUserEntity> sysUserEntityList = sysUserService.list(); result.put("title","拥有sys:user:info权限都可以查看"); result.put("data",sysUserEntityList); return ResultUtil.resultSuccess(result); }
hasPermission可以也可以和其他表达式联合使用
/** * 拥有ADMIN角色和sys:role:info权限可以访问 * @Author Sans * @CreateTime 2019/10/2 14:22 * @Return Map<String,Object> 返回数据MAP */ @PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')") @RequestMapping(value = "/adminRoleList",method = RequestMethod.GET) public Map<String,Object> adminRoleList(){ Map<String,Object> result = new HashMap<>(); List<SysRoleEntity> sysRoleEntityList = sysRoleService.list(); result.put("title","拥有ADMIN角色和sys:role:info权限可以访问"); result.put("data",sysRoleEntityList); return ResultUtil.resultSuccess(result); }
8、测试
登录USER角色账号,登录成功后我们会获取到身份认证的Token
访问USER角色的接口,把上一步获取到的Token设置在Headers中,Key为Authorization,我们之前实现的JWTAuthenticationTokenFilter拦截器会根据请求头中的Authorization获取并解析Token
好的,结束啦!!!
github源码地址:
https://github.com/dengwei-java/spring-security-Jwt-oauth2-study/tree/master/spring-security-jwt