作者在配置spring security授权过滤器时,添加自定义的AccessDeniedHandler(授权失败处理器)时候没有生效,问题代码如下
@Slf4j
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthorizationManager jwtAuthorizationManager;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
/**
* @description: securityFilterChain <br>
* @version: 1.0 <br>
* @create: 2023/3/31 9:39 <br>
* @param: http
* @return org.springframework.security.web.SecurityFilterChain
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("初始化自定义【securityFilterChain】配置");
//禁用登录表单
http.formLogin().disable();
//禁用基本身份认证
http.httpBasic().disable();
//禁用session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//禁用跨域防护
http.csrf().disable();
//授权配置
http.authorizeHttpRequests()
//开放登录请求
.antMatchers("/login/**").permitAll()
//开放swagger请求
.antMatchers("/swagger-ui/**","/swagger-resources/**","/v3/api-docs/**","/doc.html","/webjars/**","/favicon.ico").permitAll()
//jwt授权管理器
.anyRequest().access(jwtAuthorizationManager)
//授权失败处理器
.and().exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
return http.build();
}
}
按照官网的说法,当授权失败时会抛出AccessDeniedException,ExceptionTranslationFilter捕获到AccessDeniedException后交给AccessDeniedHandler处理。官网流程图如下
授权流程图
ExceptionTranslationFilter流程图
实际情况是,作者自定义了AccessDeniedHandler后,但是在授权失败时,并没有被调用。通过调试,在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 (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Sending %s to access denied handler since access is denied", authentication), exception);
}
this.accessDeniedHandler.handle(request, response, exception);
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied", authentication), exception);
}
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
}
}
代码解读:在处理AccessDeniedException时,先判断当前用户是否为匿名或记住我登录的,不是则交给AccessDeniedHandler处理,是则调用sendStartAuthentication(最终结果是返回一个403错误)。
由于作者是用的是jwt验证,所有没有配置登录验证的环节,那么所有请求进来就默认给了匿名登录的authentication。原因找到了,作者尝试禁用匿名登录和记住我,问题得以解决。
//禁用匿名访问
http.anonymous().disable();
//关闭记住我
http.rememberMe().disable();
总结
spring security过滤器的执行顺序默认是先登录认证再授权认证,并且登录认证模块默认是开启匿名登录的,如果请求没有携带登录认证信息,那么默认的登录认证结果就是匿名登录,匿名登录的授权失败时回调用sendStartAuthentication方法,而不是accessDeniedHandler。所以要使授权失败时调用accessDeniedHandler,则要保证当前用户不是匿名和记住我登录的。至于要调用自定义的accessDeniedHandler,重写下就行
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(accessDeniedException.getMessage());
}
}