【问题标题】:Implement custom exception handler for error Forbidden为错误 Forbidden 实现自定义异常处理程序
【发布时间】:2020-09-26 09:47:02
【问题描述】:

我想创建自定义异常处理程序,它返回带有数据的结构化 JSON 响应。我试过这个:

@ExceptionHandler({ AccessDeniedException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final AccessDeniedException ex) {
    ErrorDetail errorDetail = ErrorDetail.NOT_FOUND;

    LOG.error(ex.getMessage(), ex.getCause());
    ErrorResponse errorEntry = new ErrorResponse();
    .......

    return new ResponseEntity<ErrorResponseDTO>(errorResponse, HttpStatus.FORBIDDEN);
}

完整代码:Github

但我得到了这个通用结果:

{
  "timestamp": "2020-06-06T22:46:13.815+00:00",
  "status": 403,
  "error": "Forbidden",
  "message": "",
  "path": "/engine/users/request"
}

我只想得到这个结果:

{
  "errors": [{
    "status": 404,
    "code": "1000",
    "title": "Forbidden",
    "detail": "Forbidden",
    "extra": {
      "detail": "Forbidden"
    }
  }]
}

您知道如何发送自定义结果以及如何解决此问题吗?

【问题讨论】:

  • 在我的情况下,我想使用带有注释 @ExceptionHandler({ AccessDeniedException.class }) 的 Java 类,因为我想将值存储到 Enum 中。是否可以在我的处理程序中执行此操作?
  • 我非常怀疑@ExceptionHandler 是否可行。原因是,Spring 安全性对错误响应的处理方式不同,它永远不会出现在 ExceptionHandler 中,因为响​​应会在其他地方返回。
  • 可能会在自定义 AccessDeniedHandler 实现中抛出异常,以便它涉及到 ExceptionHandler,但这不是处理响应的好方法。
  • 你能给我举个例子吗?

标签: java spring spring-boot


【解决方案1】:

实现自定义异常处理程序:

  1. web.xml 中指定处理所有错误类型和异常的端点地址:
    <error-page>
        <location>/error</location>
    </error-page>
    
  2. 添加对应的错误处理方法:

    @RequestMapping(value = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> handleError(
            @RequestAttribute(name = "javax.servlet.error.status_code",
                    required = false) Integer errorCode,
            @RequestAttribute(name = "javax.servlet.error.exception",
                    required = false) Throwable exception) {
    
        if (errorCode == null) {
            errorCode = 500;
        }
    
        String reasonPhrase;
        if (exception != null) {
            Throwable cause = exception.getCause();
            if (cause != null) {
                reasonPhrase = cause.getMessage();
            } else {
                reasonPhrase = exception.getMessage();
            }
        } else {
            reasonPhrase = HttpStatus.valueOf(errorCode).getReasonPhrase();
        }
    
        HashMap<String, String> serverResponse = new HashMap<>();
        serverResponse.put("errorCode", String.valueOf(errorCode));
        serverResponse.put("reasonPhrase", reasonPhrase);
    
        return ResponseEntity.status(errorCode).body(serverResponse);
    }
    
  3. 从前端发送ajax 请求(我写在AngularJS):

    http({
        url: '/someEndpoint',
        method: "POST",
        headers: {'Content-Type': undefined },
    }).then((response) => successCallback(response),
        (response) => errorCallback(response));
    
    let successCallback = function(response) {
        // console.log(response);
        mdToast.show({
            position: 'bottom right',
            template: scope.templates.success('Success message'),
        });
    };
    
    let errorCallback = function(response) {
        // console.log(response);
        mdToast.show({
            position: 'bottom right',
            template: scope.templates.error('Error message'),
        });
        if (response && response.data && response.data.reasonPhrase) {
            mdToast.show({
                position: 'bottom right',
                template: scope.templates.error(response.data.reasonPhrase),
            });
        }
    };
    

    首先它说有一个错误,然后说它是什么错误。


例如,如果服务器返回了意外的东西:

@GetMapping({"/", "/index.html"})
public ResponseEntity<?> getMainPage() throws Exception {
    throw new Exception("Something unexpected..");
}

客户端收到这种JSON消息:

{
    "reasonPhrase": "Something unexpected..",
    "errorCode": "500"
}

或者服务器可能会返回一些不太出乎意料的东西:

@GetMapping({"/", "/index.html"})
public ResponseEntity<?> getMainPage() {
    try {
        // expected actions
        // . . .
    } catch (Exception e) {
        HashMap<String, String> serverResponse = new HashMap<>();
        serverResponse.put("errorCode", "403"));
        serverResponse.put("reasonPhrase", "Forbidden");

        return ResponseEntity.status(403).body(serverResponse);
    }
}

客户:

{
    "reasonPhrase": "Forbidden",
    "errorCode": "403"
}

另外,如果客户端请求一个无法访问的资源或一个不存在的页面,那么他会收到:

{
    "reasonPhrase": "Not Found",
    "errorCode": "404"
}

【讨论】:

    【解决方案2】:

    只需创建一个 DTO 类,然后作为正常响应返回:

    class Error {
        long status;
        long code;
        String title;
        String detail;
        Extra extra;
    
        // constructor for all argument...
    }
    

    在handler内部创建一个上述类的对象,并将对象发送到ResponseEntity:

    Error error = new Error(args....);
    return new ResponseEntity<Object>(error, HttpStatus.FORBIDDEN);
    

    【讨论】:

      【解决方案3】:

      好吧,我认为您可以通过这种方式对拒绝访问异常进行自定义错误

      import com.fasterxml.jackson.databind.ObjectMapper;
      import lombok.AllArgsConstructor;
      import lombok.Builder;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.http.MediaType;
      import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      
      import javax.servlet.ServletOutputStream;
      import javax.servlet.http.HttpServletResponse;
      
      @Configuration
      public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
      
          private final ObjectMapper om;
      
          public SecurityConfiguration() {
              this.om = new ObjectMapper();
              this.om.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
          }
      
          @Override
          protected void configure(final HttpSecurity http) throws Exception {
              http
                  .exceptionHandling()
                  .accessDeniedHandler((request, response, accessDeniedException) -> {
                      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                      response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                      ServletOutputStream out = response.getOutputStream();
                      om.writeValue(
                          out,
                          ErrorDto
                          .builder()
                              .code(1000)
                              .title("Forbidden")
                              .detail("Forbidden")
                              .status(404)
                          .build()
                      );
                      out.flush();
                  }).and()
                  .authorizeRequests().antMatchers("/api/**").authenticated()
                  .and()
                  .httpBasic()
                  .and()
                  .formLogin().disable();
          }
      
          @Data
          @AllArgsConstructor
          @NoArgsConstructor
          @Builder
          private static class ErrorDto{
              private int status;
              private int code;
              private String title;
              private String detail;
              // other fields
          }
      }
      
      

      【讨论】:

      • 谢谢,还有一个问题。我得到了一些额外的参数。你知道为什么以及如何删除它们吗?见这里:stackoverflow.com/questions/62311702/…
      • 检查属性 spring.jackson.default-property-inclusion: non_null 的应用程序设置
      • 这个设置有几个选项:ALWAYS、NON_NULL、NON_ABSENT、NON_EMPTY、NON_DEFAULT、CUSTOM、USE_DEFAULTS,如果你想单独设置对象映射器,那么最好不要注入它,而是创建新的一个并针对具体案例进行配置
      • 你能告诉我如何为对象映射器使用单独的设置吗?
      • 是的,当然。检查我的答案。刚刚更新了。查看构造函数,我在其中创建了新的 ObjectMapper
      【解决方案4】:

      使用writeValueAsString后返回一个字符串:

      return new ResponseEntity<String>(
              new ObjectMapper().writer().writeValueAsString(errorObj),
              HttpStatus.FORBIDDEN);
      

      【讨论】:

      • 你能扩展一下这个例子吗?我必须在哪里返回此响应?
      • @PeterPenzov 替换最后一行
      • @PeterPenzov 你能显示异常吗?也许不是AccessDeniedException
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-10
      • 2019-12-20
      • 2014-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      相关资源
      最近更新 更多