我在Spring Security中配置了两个异常处理,一个是自定AuthenticationEntryPoint,一个是自定义AccessDeniedHandler。但发现无论抛什么异常都进入了AuthenticationEntryPoint。怎么样才能进入自定义AccessDeniedHandler呢?往下看
认证代码
/**
* 权限控制
* 判断用户角色
* @author 刘昌兴
*
*/
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
/**
* @author 刘昌兴
* @param authentication 调用方法的调用者(非空)
* @param o 被调用的受保护对象
* @param collection 与被调用的受保护对象关联的配置属性
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//collection即是在UrlOfMenuJudgeRoleFilter中getAttributes返回的由角色组成的List
for(ConfigAttribute configAttribute:collection){
//当前url所需要的角色
String urlNeedRole=configAttribute.getAttribute();
//如果匿名可访问就不用匹配角色
if("ROLE_anonymous".equals(urlNeedRole)){
//如果未登录,提示登陆
if(authentication instanceof AnonymousAuthenticationToken){
throw new AccessDeniedException("尚未登陆,请登录");
}else{
return;//终止继续匹配角色
}
}
//获得用户所授予的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//判断用户的角色是否满足访问该url的角色
for(GrantedAuthority grantedAuthority:authorities){
if(grantedAuthority.getAuthority().equals(urlNeedRole)){
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
自定义AuthenticationEntryPoint
/**
* 用户未登录或token失效时的返回结果
* @author 刘昌兴
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter printWriter=response.getWriter();
ResultBean resultBean=ResultBean.error(authException.getMessage(), null);
resultBean.setCode(401);
printWriter.write(new ObjectMapper().writeValueAsString(resultBean));
printWriter.flush();
printWriter.close();
}
}
自定义AccessDeniedHandler
/**
* 没有权限访问时返回的结果
* @author 刘昌兴
*
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter printWriter=response.getWriter();
ResultBean resultBean=ResultBean.error("权限不足,请联系管理员!", null);
resultBean.setCode(403);
printWriter.write(new ObjectMapper().writeValueAsString(resultBean));
printWriter.flush();
printWriter.close();
}
}
Spring Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
/* .antMatchers("/login","/doc.html","/swagger-resources/**",
"/v2/api-docs/**","/webjars/**","/capture","/test/**","/ws/**","/logOut",
"/admins/userFaces","/index.html","/css/**","/js/**","/fonts/**").permitAll()//放行相关请求和资源*/
.anyRequest().authenticated()//除了上面的其他都需要认证
.withObjectPostProcessor(getObjectPostProcessor())//动态权限配置
.and()
.addFilterBefore(getJwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)//添加登陆过滤器
.exceptionHandling()//添加异常处理过滤器
.accessDeniedHandler(restfulAccessDeniedHandler)//访问拒绝处理器
.authenticationEntryPoint(restAuthenticationEntryPoint)//权限异常过滤器
.and()
.csrf().disable()//使用jwt,不需要使用csrf拦截器
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//不需要使用session
.and()
.headers().cacheControl();//禁用缓存
}
在ExceptionTranslationFilter源码中有如下代码
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
authentication), exception);
}
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(
this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
if (logger.isTraceEnabled()) {
logger.trace(
LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
exception);
}
this.accessDeniedHandler.handle(request, response, exception);
}
}
如果程序抛出了AccessDeniedException但是当前认证状态是匿名的(未认证),那么会ExceptionTranslationFilter会抛出InsufficientAuthenticationException。而所有的AuthenticationException会被配置的AuthenticationEntryPoint实现类(RestAuthenticationEntryPoint)捕获。
所以通过抛出AccessDeniedException进入自定义AccessDeniedHandler(RestfulAccessDeniedHandler)的前提是当前已完成身份认证。
修改后的认证代码
/**
* 权限控制
* 判断用户角色
* @author 刘昌兴
*
*/
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
/**
* @author 刘昌兴
* @param authentication 调用方法的调用者(非空)
* @param o 被调用的受保护对象
* @param collection 与被调用的受保护对象关联的配置属性
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//collection即是在UrlOfMenuJudgeRoleFilter中getAttributes返回的由角色组成的List
//如果未登录,提示登陆
if(authentication instanceof AnonymousAuthenticationToken){
throw new BadCredentialsException("尚未登陆");
}
for(ConfigAttribute configAttribute:collection){
//当前url所需要的角色
String urlNeedRole=configAttribute.getAttribute();
//如果URL登录即可访问就不用匹配角色
if("ROLE_login".equals(urlNeedRole)){
return;
}
//获得用户所授予的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//判断用户的角色是否满足访问该url的角色
for(GrantedAuthority grantedAuthority:authorities){
if(grantedAuthority.getAuthority().equals(urlNeedRole)){
return;
}
}
}
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
总结:如果想通过抛出AccessDeniedException异常进入自定义AccessDeniedHandler那么当前认证状态不应该是匿名的。