Spring Boot 从异常处理程序返回 401 状态自定义对象

Posted

技术标签:

【中文标题】Spring Boot 从异常处理程序返回 401 状态自定义对象【英文标题】:Spring Boot Returning 401 Statused Custom Object From Exception Handler 【发布时间】:2018-12-07 14:25:26 【问题描述】:

我有一个 Spring Boot REST API,它有一个 LoginController 用于执行简单的身份验证过程。这是令牌验证操作。

@PostMapping("validatetoken")
public ResponseEntity<ValidateTokenResponse> validateToken(
        @RequestBody ValidateTokenRequest validateTokenRequest, HttpServletRequest request) throws Exception 

    if (StringUtils.isEmpty(validateTokenRequest.getToken())) throw new AuthenticationFailedException("token parameter cannot be null or empty");

    Boolean isValid = authenticationService.validateToken(request, validateTokenRequest.getToken());
    ValidateTokenResponse response = new ValidateTokenResponse();
    response.setIsValid(isValid);
    response.setToken(validateTokenRequest.getToken());
    return new ResponseEntity<>(response, isValid? HttpStatus.OK : HttpStatus.UNAUTHORIZED);

在我的 api 中,我捕获了 ResponseEntityExceptionHandler 中的所有错误,并以这种方式转换自定义对象。

@ExceptionHandler(AuthenticationFailedException.class)
@ResponseBody
protected ResponseEntity<Object> handleAuthenticationFailed(AuthenticationFailedException ex) 
    LogManager.error(ex);
    ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
    apiError.setMessage(ex.getMessage());
    return buildResponseEntity(apiError);

但是当我想像下面这样使用RestTemplate 调用这个 api 时,我得到了像 java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode 这样的异常。

ResponseEntity<String> responseEntity =
    restTemplate.exchange(
        this.validateTokenUrl,
        HttpMethod.POST,
        requestHttpEntity,
        String.class);

但是,如果我将 HttpStatusExceptionHandler 更改为 HttpStatus.UNAUTHORIZED 除外,我可以从客户端获得真正的 ApiError 对象。什么可能导致此问题,我该如何解决?

编辑:创建了一个 github repo 来模仿我项目中的问题部分。

【问题讨论】:

我只是尝试使用“Controller”和“ControllerAdvice”而不是“RestController”和“RestControllerAdvice”注释。异常处理程序开始工作。 异常处理程序同时使用 Controller 和 RestController 以及两种类型的 Advices。我的问题是关于当 api 抛出 AuthenticationException 时从客户端获取 api 调用的结果。客户端在restTemplate.exchange() 上遇到错误,例如““localhost:8080/api/auth/validatetoken”的 POST 请求出现 I/O 错误:由于服务器身份验证,在流模式下无法重试”。所以我无法从 api 响应中获取 ApiError 类实例和详细信息。 【参考方案1】:

为了清楚起见,我建议在单独的配置文件中使用以下详细信息自动装配 resttemplate

@Bean
  public RestTemplate restTemplate() 
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setErrorHandler(new ErrorHandler());

    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    requestFactory.setOutputStreaming(false);

    restTemplate.setRequestFactory(requestFactory);

    return restTemplate;
  

【讨论】:

【参考方案2】:

默认情况下RestTemplate 使用DefaultResponseErrorHandler。每当从 REST API 发送 4xx/5xx 响应时,此错误处理程序都会引发异常。

如果您想要自定义错误处理,只需通过restTemplate.setErrorHandler 注册您的自定义错误处理程序,您将在其中使用ResponseErrorHandler 接口的实现。

【讨论】:

我已经在使用实现DefaultResponseErrorHandler 的错误处理程序。但它不起作用。我已将项目的相关部分推送到 github repo here。【参考方案3】:

这是 SimpleClientHttpRequestFactory 的默认行为(使用 JDK 的内部 http 客户端)

一个简单的解决方法是使用 apache http 组件库:

ClientHttpRequestFactory  requestFactory = new HttpComponentsClientHttpRequestFactory();
restTemplate.setRequestFactory(requestFactory);

查看此链接了解更多详情:https://github.com/spring-projects/spring-security-oauth/issues/441#issuecomment-92033542

【讨论】:

以上是关于Spring Boot 从异常处理程序返回 401 状态自定义对象的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot @ControllerAdvice 异常处理程序未触发

为啥 Spring Boot Actuator 返回 404 而不是 401?

Spring Boot异常处理

对于未经身份验证的请求,Keycloak spring boot 返回 403 而不是 401

Spring Boot 处理异常返回json

测试开发专题:spring-boot自定义异常返回