在我们开发系统时,返回给前端的信息均具有统一的格式,但在使用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异常信息的自定义。

效果如下:

改造前:

改造后:


版权声明:本文为dechengtju原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/dechengtju/article/details/107773070