SpringSecurityOAuth2自定义异常

在使用 SpringSecurityOAuth2 中,用户名或密码失败可以使用 InvalidGrantException 来捕获,账户禁用等异常可以使用 InternalAuthenticationServiceException 来捕获,但是客户端认证失败(无效的凭据)无法使用全局异常处理器来捕获,因为是发生在 filter ,此时还没有到 DispatcherServlet 请求处理流程上,我们可以查看源码进行分析,找到对应的解决方案

认证流程

客户端的认证处理顺序是 ClientCredentialsTokenEndpointFilter - OAuth2AuthenticationEntryPoint - AbstractOAuth2SecurityExceptionHandler#doHandle,可以看到 result 就是返回的结果。

image.png

image.png

image.png

解决方案

既然已经知道认证失败异常是通过 OAuth2AuthenticationEntryPoint 来得到响应结果的,所以我们可以重写 ClientCredentialsTokenEndpointFilter 并使用自定义的 OAuth2AuthenticationEntryPoint 进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

/**
* 自定义异常响应数据
*
* @return
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, e) -> {
log.error("无效凭据:", e);
response.setStatus(HttpStatus.OK.value());
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
ServerResponse serverResponse = ServerResponse.createByError(ResultEnum.INVALID_CLIENT);
response.getWriter().print(JSONUtil.toJson(serverResponse));
response.getWriter().flush();
};
}

/**
* 配置自定义filter
*
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());

security.addTokenEndpointAuthenticationFilter(endpointFilter);
security.authenticationEntryPoint(authenticationEntryPoint())
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 自定义filter,处理错误的凭据,用于统一异常信息
*
* @author chenkaixin
* @since 2021/9/9
*/
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {

private AuthorizationServerSecurityConfigurer configurer;

private AuthenticationEntryPoint authenticationEntryPoint;

public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
this.configurer = configurer;
}

@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
super.setAuthenticationEntryPoint(null);
this.authenticationEntryPoint = authenticationEntryPoint;
}

@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}

@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler((request, response, exception) -> authenticationEntryPoint.commence(request, response, exception));
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
}
}

测试

原来的返回结果为

1
2
3
4
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}

现在的返回结果

1
2
3
4
{
"status": 1060,
"msg": "无效凭据"
}