问题遇到的现象和发生背景
我在Spring Security中配置了两个异常处理,一个是自定AuthenticationEntryPoint,一个是自定义AccessDeniedHandler。但发现无论抛什么异常都进入了AuthenticationEntryPoint。怎么样才能进入自定义AccessDeniedHandler呢?往下看
问题相关代码
认证代码
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
for(ConfigAttribute configAttribute:collection){
String urlNeedRole=configAttribute.getAttribute();
if("ROLE_anonymous".equals(urlNeedRole)){
if(authentication instanceof AnonymousAuthenticationToken){
throw new AccessDeniedException("尚未登陆,请登录");
}else{
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
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
@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
@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()
.anyRequest().authenticated()
.withObjectPostProcessor(getObjectPostProcessor())
.and()
.addFilterBefore(getJwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.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)的前提是当前已完成身份认证。 修改后的认证代码
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
if(authentication instanceof AnonymousAuthenticationToken){
throw new BadCredentialsException("尚未登陆");
}
for(ConfigAttribute configAttribute:collection){
String urlNeedRole=configAttribute.getAttribute();
if("ROLE_login".equals(urlNeedRole)){
return;
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
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那么当前认证状态不应该是匿名的。
|