【问题标题】:Customize auth error from Spring Security using OAuth2使用 OAuth2 自定义来自 Spring Security 的身份验证错误
【发布时间】:2018-02-09 15:18:09
【问题描述】:

我想知道是否可以自定义以下授权错误:

{
  "error": "unauthorized",
  "error_description": "Full authentication is required to access this resource"
}

当用户请求没有权限时我得到它。而且我想将其自定义为与 Spring Boot 错误非常相似:

{
 "timestamp":1445441285803,
 "status":401,
 "error":"Unauthorized",
 "message":"Bad credentials",
 "path":"/oauth/token"
}

有可能吗?

非常感谢。

【问题讨论】:

    标签: java security spring-boot spring-security spring-security-oauth2


    【解决方案1】:

    接受的答案对我使用 Oauth2 不起作用。经过一番研究,exception translator solution 起作用了。

    基本上,您需要创建一个WebResponseExceptionTranslator 并将其注册为您的异常翻译器。

    首先,创建一个WebResponseExceptionTranslator bean:

    @Slf4j
    @Configuration
    public class Oauth2ExceptionTranslatorConfiguration {
    
        @Bean
        public WebResponseExceptionTranslator oauth2ResponseExceptionTranslator() {
            return new DefaultWebResponseExceptionTranslator() {
    
                @Override
                public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
    
                    ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                    OAuth2Exception body = responseEntity.getBody();
                    HttpStatus statusCode = responseEntity.getStatusCode();
    
                    body.addAdditionalInformation("timestamp", dateTimeFormat.format(clock.instant()))
                    body.addAdditionalInformation("status", body.getHttpErrorCode().toString())
                    body.addAdditionalInformation("message", body.getMessage())
                    body.addAdditionalInformation("code", body.getOAuth2ErrorCode().toUpperCase())
    
                    HttpHeaders headers = new HttpHeaders();
                    headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                    // do something with header or response
                    return new ResponseEntity<>(body, headers, statusCode);
                }
            };
        }
    
    }
    

    现在您需要更改 Oauth2 配置以注册 bean WebResponseExceptionTranslator

    @Slf4j
    @Configuration
    public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private ClientDetailsServiceBuilder builder;
    
        @Autowired
        private WebResponseExceptionTranslator oauth2ResponseExceptionTranslator;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) {
            clients.setBuilder(builder);
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    
            tokenEnhancerChain.setTokenEnhancers(
                    Arrays.asList(tokenEnhancer(), accessTokenConverter()));
    
            endpoints.tokenStore(tokenStore())
                    .tokenEnhancer(tokenEnhancerChain)
                    .authenticationManager(authenticationManager)
                    .userDetailsService(userDetailsService)
                    .exceptionTranslator(oauth2ResponseExceptionTranslator);
    
        }
    
    }
    

    最终结果将是:

    {
        "error": "unauthorized",
        "error_description": "Full authentication is required to access this resource",
        "code": "UNAUTHORIZED",
        "message": "Full authentication is required to access this resource",
        "status": "401",
        "timestamp": "2018-06-28T23:55:28.86Z"
    }
    

    您可以看到我没有从OAuth2Exception 的原始正文中删除errorerror_description。我建议维护它们,因为这两个字段遵循 OAuth2 规范。请参阅the RFCOAuth2 API definitions 了解更多详情。

    您还可以自定义结果:覆盖errorerror_description(只需调用addAdditionalInformation),使用instance of识别特定异常以返回不同的json结果等。但也有限制:如果你想将某个字段定义为integer,我认为这是不可能的,因为addAdditionalInformation 方法只接受String 作为类型。

    【讨论】:

    • @SledgeHammer 这对我使用此解决方案的项目非常有效。验证您的 Spring 版本,我使用了一些 1.5.X 版本。你注册了异常翻译器吗?翻译器直接注册在 Spring 配置上,不属于我的解决方案。
    • Spring Boot 2.1.9 版本的魅力所在
    • 如何删除现有的error_descriptionerror ??
    • @aswzen 我真的不记得了:(
    • 对于任何想知道如何删除/替换“错误”和“错误描述”的人,这里有一篇描述它的文章(基于 WebResponseExceptionTranslator)medium.com/@beladiyahardik7/…
    【解决方案2】:

    我明白了:)

    https://stackoverflow.com/a/37132751/2520689

    我需要创建一个实现“AuthenticationEntryPoint”的新类,如下所示:

    public class AuthExceptionEntryPoint implements AuthenticationEntryPoint
    {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException, ServletException
        {
            final Map<String, Object> mapBodyException = new HashMap<>() ;
    
            mapBodyException.put("error"    , "Error from AuthenticationEntryPoint") ;
            mapBodyException.put("message"  , "Message from AuthenticationEntryPoint") ;
            mapBodyException.put("exception", "My stack trace exception") ;
            mapBodyException.put("path"     , request.getServletPath()) ;
            mapBodyException.put("timestamp", (new Date()).getTime()) ;
    
            response.setContentType("application/json") ;
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) ;
    
            final ObjectMapper mapper = new ObjectMapper() ;
            mapper.writeValue(response.getOutputStream(), mapBodyException) ;
        }
    }
    

    并将其添加到我的 ResourceServerConfigurerAdapter 实现中:

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
    {   
        @Override
        public void configure(HttpSecurity http) throws Exception
        {
            http.exceptionHandling().authenticationEntryPoint(new AuthExceptionEntryPoint()) ;
    
        }
    }
    

    你可以找到我的 GitHub 项目,它实现了你需要的一切:

    https://github.com/pakkk/custom-spring-security

    【讨论】:

    • 该死,OAuthException 本身在哪里?
    • No man,AuthenticationEntryPoint 在没有提供凭据时使用,确实开始方法是不言自明的,它说明了您将如何告诉用户他应该开始身份验证,这与让他知道不同凭据无效。无论如何,继续提供无效凭据,您将看到您的入口点没有被触发。
    • 好吧,我已经查看了源代码,但我错了,但我仍然不同意你的观点(虽然我没有测试你的代码,但我的应用程序中有类似的设置)。请阅读我的回复,我提供了更多详细信息。
    【解决方案3】:

    故事简介: https://github.com/melardev/JavaSpringBootOAuth2JwtCrudPagination.git

    在阅读了@pakkk 的回复后,我不同意,所以我决定尝试自己的想法,但也失败了,所以我决定看一下 Spring Security 源代码本身,会发生什么: 有一个过滤器很早就被调用,即 OAuth2AuthenticationProcessingFilter。 此过滤器尝试从标头中提取 JWT,如果抛出异常,它会调用 它的 authenticationEntryPoint.commence() (@pakk 就在这里) 我试图添加一个过滤器来检查它是否在 Jwt 无效或存在时被调用,但它没有,因此,添加一个自定义过滤器来更改响应将不起作用。 然后我查看了 OAuth2AuthenticationProcessingFilter 的配置位置,我发现它是在 ResourceServerSecurityConfigurer::configure(HttpSecurity http) 上设置的。 话虽如此,让我们看看我们如何才能融入这个过程。 事实证明这很容易,因为您将在资源服务器应用程序中扩展 ResourceServerConfigurerAdapter 类:

    @Configuration
    @EnableResourceServer
    public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    // ....
    }
    

    您继续并覆盖:

    @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
    }
    

    如您所见,是的!您可以访问 ResourceServerSecurityConfigurer,那么现在呢? 好吧,让我们用我们的替换默认入口点:

    @Autowired
        private AuthenticationEntryPoint oauthEntryPoint;
    @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
    
            resources.authenticationEntryPoint(oauthEntryPoint);
        }
    

    有关示例的完整源代码,请查看: https://github.com/melardev/JavaSpringBootOAuth2JwtCrudPagination.git

    如果没有这些步骤,至少对我来说是行不通的,@pakkk 提供的响应对我不起作用,我检查了调试器,默认情况下使用的入口点不是我们的,即使使用:

    http.and().exceptionHandling().authenticationEntryPoint(oauthEntryPoint)
    

    这是我测试的第一件事,要使其正常工作,您必须直接从 ResourceServerSecurityConfigurer 类更改入口点。

    这是我的入口点:注意我发送的是我自己的类的 ErrorResponse 对象,所以我可以完全控制响应:

    @Component
    public class OAuthEntryPoint implements AuthenticationEntryPoint {
    
        @Autowired
        ObjectMapper mapper;
    
        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
            ServletServerHttpResponse res = new ServletServerHttpResponse(httpServletResponse);
            res.setStatusCode(HttpStatus.FORBIDDEN);
            res.getServletResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            res.getBody().write(mapper.writeValueAsString(new ErrorResponse("You must authenticated")).getBytes());
        }
    }
    

    【讨论】:

      【解决方案4】:

      我认为您可以使用@ControllerAdvice 来捕获未经授权的异常,然后按照您的期望格式化响应并返回它。像这样的:

      @ResponseBody
      @ExceptionHandler(CustomException.class)
      @ResponseStatus(value=HttpStatus.UNAUTHORIZED, reason="Exception message")
      public JsonResponse unAuthorised(HttpServletRequest request, Exception ex) {
          return new JsonResponse("ERROR", 401, "Unauthorised Request");
      }
      

      希望对您有所帮助。

      【讨论】:

      • 这种错误类型永远不会被之前的@ControllerAdvice 捕获:(
      • ControllerAdvice 将不起作用,因为在调用控制器(包括 ControllerAdvice)之前会处理身份验证。
      猜你喜欢
      • 2015-04-27
      • 2016-02-09
      • 2015-07-27
      • 2014-04-20
      • 2014-12-13
      • 2016-08-05
      • 2020-12-05
      • 2019-09-22
      • 2014-01-12
      相关资源
      最近更新 更多