在我们开发系统时,返回给前端的信息均具有统一的格式,但在使用spring security oauth2时,遇到client_id或client_secret错误时,返回信息为如下格式:
{
"timestamp": "2020-08-03T20:41:54.776+0800",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/uaa/oauth/token"
}
上述格式并不符合我们的开发规范,因此需要对spring security oauth2返回的信息进行处理。
通过debug发现,当通过设置header信息”Authorization Basic xeeEOnBpZzE=”并提交认证时,oauth2使用的是Basic方式的认证,使用的过滤器为BasicAuthenticationFilter,最终使用BasicAuthenticationEntryPoint来生成返回信息。
经过各种尝试后,终于发现解决oauth2返回统一信息的方法。
解决方案如下:
在configure(AuthorizationServerSecurityConfigurer oauthServerSecurity) {}中新增如下代码:
oauthServerSecurity.addTokenEndpointAuthenticationFilter(
new BasicAuthenticationFilter(authenticationManager, customAuthenticationEntryPoint));
这样便可以覆盖默认的Basic authentication过滤器,并实现自定义的错误响应信息。
完整代码如下:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServerSecurity) {
oauthServerSecurity.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
oauthServerSecurity.addTokenEndpointAuthenticationFilter(
new BasicAuthenticationFilter(authenticationManager, customAuthenticationEntryPoint));
}
自定义CustomAuthenticationEntryPoint
@Component("customAuthenticationEntryPoint")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.OK.value());
R r = R.error(HttpStatus.UNAUTHORIZED.value(), "client_id或client_secret错误");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(r));
response.getWriter().flush();
}
}
但此处有坑:
因password模式需要注入authenticationManagerBean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
因此,在上述代码中如果直接使用全局注入的authenticationManager会导致其在进行client_id和client_secret校验时,使用的是UserDetailsService而不是ClientDetailsService进行验证,导致最终验证失败。
因此,需要使用自定义的authenticationManager对上边有坑的代码进行改造,改造结果如下:
oauthServerSecurity.addTokenEndpointAuthenticationFilter(
new BasicAuthenticationFilter(authenticationManager(), customAuthenticationEntryPoint));
哪么,如果自定义authenticationManager呢?
首先,我们需要定义一下ClientDetailsService
@Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(new ClientDetailsUserDetailsService(customClientDetailsService()));
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
return daoAuthenticationProvider;
}
其次,定义一个AuthenticationProvider
@Bean
ClientDetailsService customClientDetailsService() {
OauthClientDetailsService clientDetailsService = new OauthClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
return clientDetailsService;
}
最终,定义我们的自定义authenticationManager。
@Bean
public AuthenticationManager authenticationManager() {
return authentication -> daoAuthenticationProvider().authenticate(authentication);
}
至次,便完成了Oauth2认证服务器Unauthorized异常信息的自定义。
效果如下:
改造前:
改造后: