目录
1. Spring Security详细介绍
2. Spring Security详细使用
3. Spring Security实现JWT token验证
4. JWT(JSON Web Token,JSON令牌)
5. Spring Security安全注解
Spring Security实现JWT token验证
Spring Security是Spring提供的一个安全框架,提供认证和授权功能,最主要的是它提供了简单的使用方式,同时又有很高的灵活性,简单、灵活、强大
一般系统里关于角色方面通常有这么几张表:用户表、角色表、用户-角色表、菜单表、角色-菜单表、权限表、角色-权限表等
1.导入依赖
JWT认证的实现
(1)支持用户通过用户名和密码登录
(2)登录后通过http header返回token,每次请求,客户端需通过header将token带回,用于权限校验
(3)服务端负责token的定期刷新
<properties><jjwt.version>0.9.1</jjwt.version><spring-security-jwt.version>1.0.11.RELEASE</spring-security-jwt.version>
</properties><!-- Spring Security-->
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>${spring-security-jwt.version}</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jjwt.version}</version></dependency>
</dependencies>
2.Spring Security配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义登录成功处理器*/@Autowiredprivate UserLoginSuccessHandler userLoginSuccessHandler;/*** 自定义登录失败处理器*/@Autowiredprivate UserLoginFailureHandler userLoginFailureHandler;/*** 自定义注销成功处理器*/@Autowiredprivate UserLogoutSuccessHandler userLogoutSuccessHandler;/*** 自定义暂无权限处理器*/@Autowiredprivate UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;/*** 自定义未登录的处理器*/@Autowiredprivate UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;/*** 自定义登录逻辑验证器*/@Autowiredprivate UserAuthenticationProvider userAuthenticationProvider;/*** 加密方式*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 注入自定义PermissionEvaluator*/@Beanpublic DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();handler.setPermissionEvaluator(new UserPermissionEvaluator());return handler;}/*** 配置登录验证逻辑*/@Overrideprotected void configure(AuthenticationManagerBuilder auth){//启用自定义的登陆验证逻辑auth.authenticationProvider(userAuthenticationProvider);}/*** 配置security的控制逻辑* @Param http 请求*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 不进行权限验证的请求或资源(从配置文件中读取).antMatchers(JWTConfig.antMatchers.split(",")).permitAll()// .antMatchers("/*").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不需要sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 禁用缓存http.headers().cacheControl();// 添加JWT过滤器http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));}
}
3.编写JWTConfig和application.yml增加jwt相关配置
// JWT相关配置类
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {/*** 密钥KEY*/private String secret;/*** TokenKey*/private String tokenHeader;/*** Token前缀字符*/private String tokenPrefix;/*** 过期时间*/private Integer expiration;/*** 不需要认证的接口*/private String antMatchers;/*** jwt发行人*/private String issuer;/*** 主体*/private String subject;/*** 接受者*/private String audience;/*** 有效时间*/private String validTime;
}
application.yml
# JWT配置
jwt:# 密匙KEYsecret: secret# Header KEYtokenHeader: infinite-auth# Token前缀字符tokenPrefix: authorization-# 过期时间 单位秒 1天后过期=86400 7天后过期=604800expiration: 86400# 配置不需要认证的接口antMatchers: /generator/table/index# jwt发行人issuer: infinite# 主体subject: to-infinite# 接受者audience: infinite-user# 有效时间,分钟validTime: 30
4.编写过滤器处理类
(1)登录成功处理类(AuthenticationSuccessHandler)
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {/*** 登录成功返回结果*/@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){// 组装JWTSelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal();String token = JWTTokenUtil.createAccessToken(selfUserEntity);token = JWTConfig.tokenPrefix + token;// 封装返回参数Map<String,Object> resultData = new HashMap<>();resultData.put("code","200");resultData.put("msg", "登录成功");resultData.put("token",token);ResultUtil.responseJson(response,resultData);}
}
(2)登录失败处理类(AuthenticationFailureHandler)
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {/*** 登录失败返回结果*/@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){// 这些对于操作的处理类可以根据不同异常进行不同处理if (exception instanceof UsernameNotFoundException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在"));}if (exception instanceof LockedException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结"));}if (exception instanceof BadCredentialsException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"密码错误"));}ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败"));}
}
(3)登出成功处理类(LogoutSuccessHandler)
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {/*** 用户登出返回结果,前端清除掉Token*/@Overridepublic 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));}
}
(4)暂无权限处理类(AccessDeniedHandler)
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {/*** 暂无权限返回结果*/@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授权"));}
}
(5)用户未登录处理类(AuthenticationEntryPoint)
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {/*** 用户未登录返回结果*/@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录"));}
}
5.自定登录验证(AuthenticationProvider)
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UsersService usersService;@Autowiredprivate UsermetaService usermetaService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 获取表单输入中返回的用户名String userName = (String) authentication.getPrincipal();// 获取表单中输入的密码String password = (String) authentication.getCredentials();// 查询用户是否存在SelfUserEntity userInfo = usersService.getUserInfo(userName);if (userInfo.getUsername() == null || userInfo.getUsername() == "") {throw new UsernameNotFoundException("用户名不存在");}// 判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {throw new BadCredentialsException("密码不正确");}// 还可以加一些其他信息的判断,比如用户账号已停用等判断if (userInfo.getStatus().equals("1")) {throw new LockedException("该用户已被冻结");}// 角色集合Set<GrantedAuthority> authorities = new HashSet<>();EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();roleWrapper.eq("user_id",userInfo.getUserId());roleWrapper.eq("meta_key","wp_user_level");// 查询用户角色List<Usermeta> sysRoleEntityList = usermetaService.selectList(roleWrapper);for (Usermeta sysRoleEntity: sysRoleEntityList){authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getMetaValue()));}userInfo.setAuthorities(authorities);// 进行登录return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);}@Overridepublic boolean supports(Class<?> authentication) {return true;}
}
6.自定义权限注解验证(PermissionEvaluator)
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {@Autowiredprivate UsermetaService usermetaService;/*** hasPermission鉴权方法* 这里仅仅判断PreAuthorize注解中的权限表达式* 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权* 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计* @Param authentication 用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)* @Param targetUrl 请求路径* @Param permission 请求路径权限* @Return boolean 是否通过*/@Overridepublic boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {// 获取用户信息Usermeta selfUserEntity =(Usermeta) authentication.getPrincipal();// 查询用户权限(这里可以将权限放入缓存中提升效率)Set<String> permissions = new HashSet<>();EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();roleWrapper.eq("user_id",selfUserEntity.getUserId());roleWrapper.eq("meta_key","wp_user_level");List<Usermeta> sysMenuEntityList = usermetaService.selectList(roleWrapper);for (Usermeta sysMenuEntity:sysMenuEntityList) {permissions.add(sysMenuEntity.getMetaValue());}// 权限对比if (permissions.contains(permission.toString())){return true;}return true;}@Overridepublic boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {return false;}
}
7.用户实体类(UserDetails)
Spring Security用户的实体,必须要实现UserDetails接口。
public class SelfUserEntity implements Serializable, UserDetails {private static final long serialVersionUID = 1L;/*** 用户ID*/private Long userId;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 状态*/private String status;/*** 显示名称*/private String displayName;/*** 用户参数*/private Map<String, String> userParamMap;/*** 用户角色*/private Collection<GrantedAuthority> authorities;/*** 账户是否过期*/private boolean isAccountNonExpired = false;/*** 账户是否被锁定*/private boolean isAccountNonLocked = false;/*** 证书是否过期*/private boolean isCredentialsNonExpired = false;/*** 账户是否有效*/private boolean isEnabled = true;// 省略getter/setter
}
8.JWT接口请求拦截器(BasicAuthenticationFilter)
JWT接口请求校验拦截器,请求接口时会进入这里验证Token是否合法和过期
public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取请求头中JWT的TokenString tokenHeader = request.getHeader(JWTConfig.tokenHeader);if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {try {// 截取JWT前缀String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");// 解析JWTClaims claims = Jwts.parser().setSigningKey(JWTConfig.secret).parseClaimsJws(token).getBody();// 获取用户名String username = claims.getSubject();String userId = claims.getId();if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(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 (!StringUtils.isEmpty(role)) {authorities.add(new SimpleGrantedAuthority(role.get("authority")));}}}//组装参数SelfUserEntity selfUserEntity = new SelfUserEntity();selfUserEntity.setUsername(claims.getSubject());selfUserEntity.setUserId(Long.parseLong(claims.getId()));selfUserEntity.setAuthorities(authorities);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (ExpiredJwtException e) {System.out.println("Token过期");} catch (Exception e) {System.out.println("Token无效");}}filterChain.doFilter(request, response);}
}
9.Spring Security用户的业务实现
@Component
public class SelfUserDetailsService implements UserDetailsService {@Autowiredprivate UsersService usersService;/*** 查询用户信息** @Param username 用户名* @Return UserDetails SpringSecurity用户信息*/@Overridepublic SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {EntityWrapper<Users> wrapper = new EntityWrapper<>();//邮箱正则表达式String expr = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$";//是否为邮箱if (username.matches(expr)) {wrapper.eq("user_email", username);} else {wrapper.eq("user_login", username);}// 查询用户信息Users sysUserEntity = usersService.selectOne(wrapper);if (sysUserEntity != null) {// 组装参数SelfUserEntity selfUserEntity = new SelfUserEntity();BeanUtils.copyProperties(sysUserEntity, selfUserEntity);return selfUserEntity;}return null;}
}
10.控制层Controller
@Secured
当@EnableGlobalMethodSecurity(securedEnabled=true)的时候,@Secured可以使用。
@PostMapping("/helloUser")
@Secured({"ROLE_normal","ROLE_admin"})
public Map<String, Object> initDashboard() {Map<String, Object> result = new HashMap<>();result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());return ResultUtil.resultSuccess(result);
}
说明:拥有normal或者admin角色的用户都可以方法helloUser()方法。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“
@PreAuthorize
Spring的 @PreAuthorize/@PostAuthorize 注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制。
当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PreAuthorize可以使用:
@PostMapping("/initDashboard")
@PreAuthorize("hasRole('100')")
public Map<String, Object> initDashboard() {Map<String, Object> result = new HashMap<>();result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());return ResultUtil.resultSuccess(result);
}
@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。
当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PostAuthorize可以使用:
@GetMapping("/getUserInfo")
@PostAuthorize(" returnObject!=null && returnObject.username == authentication.name")
public User getUserInfo() {Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();User user;if("anonymousUser".equals(pricipal)) {user = null;} else {user = (User) pricipal;}return user;
}
11.登录测试
(1)拿到正确token,正常登录
(2)请求中如果不携带token的话,请求其它接口就会显示没有登录的提示
(3)登录账户无权限
GrantedAuthority接口
在UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization
①权限:就是一个字符串。一般不会重复
②权限检查:就是查看用户权限列表中是否含有匹配的字符串
public interface GrantedAuthority extends Serializable {// AccessDecisionManager访问控制决策,返回一个字符串String getAuthority();
}
“角色”如何表示?与Shiro有何不同?
在Spring Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀“ROLE_”,而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Spring Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等
实现类:JaasGrantedAuthority、LdapAuthority、SimpleGrantedAuthority、SwitchUserGrantedAuthority
JaasGrantedAuthority GrantedAuthority除了分配的角色之外,还拥有主体,使用AuthorityGranter作为授予此权限的理由
SwitchUserGrantedAuthority 存储原始用户的身份验证对象,以便在以后从用户交换机“退出”时使用。
GrantedAuthority接口的默认实现SimpleGrantedAuthority
注意:在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示“角色”的权限需要带有“ROLE_”前缀。Spring Security不管是角色、还是权限。它只比对字符串。所以角色需要加前缀。
角色信息存储的时候可以没有“ROLE_”前缀,包装成GrantedAuthority对象的时候必须要有
/**
* GrantedAuthority的基本具体实现。存储授予的权限的String(Authentication)
*/
public final class SimpleGrantedAuthority implements GrantedAuthority {private final String role;
}
权限检查/访问控制方式
权限检查有两种方式:
①在配置类中,指定粗粒度的访问控制
②使用注解细粒度的控制访问
(1)粗粒度访问控制
所有URL以"/admin"开头的用户必须拥有角色"ADMIN"才能访问。实际上操作的时候hasRole表达式,会判断参数是否包含"ROLE_"前缀,如果没有则加上去,然后再去校验。有这个前缀则直接校验。
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").access("hasRole('ADMIN')").antMatchers("/user/**").access("hasRole('USER')").anyRequest().authenticated();
}
(2)细粒度的访问控制
注:需要使用注解@EnableGlobalMethodSecurity(prePostEnabled=true)开启
@PreAuthoritze("hasAuthority('readArtical')")
public List<Artical> getAll() {//...
}
@PreAuthoritze注解,会从SecurityContext中取出Authencation对象,然后再取出Collection、 authorites集合。然后比对当前用户是否有权限"readArtical"。实际上就是比对集合中是否有那个GrantedAuthority的getAuthority()方法返回的字符串与"radArtical"匹配。
开启权限注解
在任何@Configuration实例上使用@EnableGlobalMethodSecurity注解就能达到此目的。同时这个注解提供prePostEnabled、securedEnabled和jsr250Enabled三种不同的机制来实现同一种功能
(1)prePostEnabled
prePostEnabled = true 会解锁@PreAuthorize和@PostAuthorize两个注解。从名字就可以看出@PreAuthorize注解会在方法执行前进行验证,而@PostAuthorize注解会在方法执行后进行验证。
public interface UserService {List<User> findAllUsers();@PostAuthorize ("returnObject.type == authentication.name")User findById(int id);// @PreAuthorize("hasRole('ADMIN')") 必须拥有ROLE_ADMIN角色。@PreAuthorize("hasRole('ROLE_ADMIN ')")void updateUser(User user);@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")void deleteUser(int id);// @PreAuthorize("principal.username.startsWith('Felordcn')") 用户名开头为 Felordcn 的用户才能访问。// @PreAuthorize("#id.equals(principal.username)") 入参 id 必须同当前的用户名相同。// @PreAuthorize("#id < 10") 限制只能查询 id 小于 10 的用户}
常见内置表达式
https://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#el-common-built-in
@PostAuthorize:
该注解使用不多,在方法执行后再进行权限验证。 适合验证带有返回值的权限。Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。区别在于先执行方法。而后进行表达式判断。如果方法没有返回值实际上等于开放权限控制;如果有返回值实际的结果是用户操作成功但是得不到响应。允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常。
@PreFilter:
对集合类型的参数执行过滤,移除结果为false的元素。基于方法入参相关的表达式,对入参进行过滤。分页慎用!该过程发生在接口接收参数之前。入参必须为 java.util.Collection且支持remove(Object)的参数。如果有多个集合需要通过 filterTarget=<参数名> 来指定过滤的集合。内置保留名称 filterObject 作为集合元素的操作名来进行评估过滤。
// 指定过滤的参数,过滤偶数
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> username)
@PostFilter:
和@PreFilter不同的是, 基于返回值相关的表达式,对返回值进行过滤。分页慎用!该过程发生接口进行数据返回之前
(2)Secured
@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。
@Secured缺点(限制)就是不支持Spring EL表达式。不够灵活。并且指定的角色必须以ROLE_开头,不可省略。该注解功能要简单的多,默认情况下只能基于角色(默认需要带前缀 ROLE_)集合来进行访问控制决策。该注解的机制是只要其声明的角色集合(value)中包含当前用户持有的任一角色就可以访问。也就是 用户的角色集合和 @Secured 注解的角色集合要存在非空的交集。 不支持使用 SpEL 表达式进行决策。
@Secured({"ROLE_user"})void updateUser(User user);@Secured({"ROLE_admin", "ROLE_user1"})void updateUser();
(3)jsr250E
启用JSR-250安全控制注解,这属于JavaEE的安全规范(现为jakarta项目)。一共有五个安全注解。如果在@EnableGlobalMethodSecurity设置jsr250Enabled为true ,就开启了JavaEE 安全注解中的以下三个:
1.@DenyAll: 拒绝所有访问
2.@RolesAllowed({“USER”, “ADMIN”}): 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省略前缀ROLE_,实际的权限可能是ROLE_ADMIN
3.@PermitAll: 允许所有访问
/** 配置没有权限自定义处理类** 注意:hasRole(),如果出现异常,会调用设置的accessDeniedHandler方法。* .antMatchers("/index").hasRole("ADMIN")* .anyRequest().authenticated();*/
httpSecurity.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler);