需求背景比较简单,就是需要区分以下两种状态:
Basic Auth 认证的场景 返回状态码
没有用户名密码 401
用户名密码错误 403
实现也比较粗暴,通过 Spring Security 的 WebSecurityConfigurerAdapter,配置自定义的 AuthenticationEntryPoint,然后判断异常的类型设置不同的状态,密码错误时的异常为BadCredentialsException,没有用户密码时的异常为InsufficientAuthenticationException,代码如下:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// omit other configures ......
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint());
}
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, exception) -> {
if (exception instanceof BadCredentialsException) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
};
}
}
之前一直很稳,但是从springboot 1.5.22 迁移到 springboot 2.3.0 之后,问题就来了,之前的代码已经无法区分这两个状态,一直返回的是401.
一顿虎操,发现现象就是 BadCredentialsException 出现时,InsufficientAuthenticationException 也会跟着出现,最终导致client端收到的状态码一直是401。
网上搜索+折腾了半天都没起作用,后来在github上发现了root cause:
Spring security 5 "Bad credentials" exception not shown with errorDetails
解决办法也比较容易,给 /error 页面添加权限即可。添加一行代码:
.antMatchers("/error").permitAll()
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/error").permitAll()
.anyRequest().authenticated()
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint());
}