【问题标题】:Overriding spring-boot's default error handler覆盖 spring-boot 的默认错误处理程序
【发布时间】:2020-11-27 03:14:51
【问题描述】:

如何覆盖 Spring Boot 错误处理的默认输出?当发生 403 响应或类似情况时,我想更改显示的默认值。

现在,我在过滤器链的 UsernamePasswordAuthenticationFilter 之前添加了一个扩展 OncePerRequestFilter 的类。在我的自定义过滤器中,我在 doFilterInternal 方法中检查 JWT 令牌是否已过期。

如果已经过期,我将状态设置为 403 response.setStatus(HttpStatus.SC_FORBIDDEN); 并写入内容

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {

        ...


        if (jwtTokenUtil.isTokenExpired(jwtToken)) {
                response.setStatus(HttpStatus.SC_FORBIDDEN);
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.getWriter().write(error.toString());
                response.getWriter().flush();
                response.getWriter().close();
        }

        ...

    }

如果我设置了状态码,不写内容,没有异常,用户会得到正确的状态码,但是内容是由Spring自动创建的。

如果我设置状态码并写入内容,那么从用户的角度来看,一切实际上都是有效的,但在内部发生了一个异常,谈论响应是如何写入的。

我想以正确的方式做事;可能有一些类要覆盖和自定义,以便我可以根据错误代码自定义内容,但我无法找到任何相关信息。

编辑: 如果我尝试写入正文,这是内部抛出的异常

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Unable to handle the Spring Security Exception because the response is already committed.] with root cause

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:123) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
    at com.abc.web.config.JwtRequestFilter.doFilterInternal(JwtRequestFilter.java:130) ~[classes/:na]

【问题讨论】:

    标签: spring-boot http-status-codes http-error


    【解决方案1】:

    我建议您创建一个自定义异常,例如 - TokenExpiredException,而不是在过滤器中设置响应状态,并将其扔到代码中的 if 块中 -

    if (jwtTokenUtil.isTokenExpired(jwtToken)) {
        // ... some code ... //
        throw new TokenExpiredException("Token Expired");
        // ... some code ... //        
    }
    

    然后您可以创建一个@ControllerAdvice,它可以作为处理所有异常的中心类。代码中将有明确的关注点分离。同样可以通过以下方式完成 -

    您的 application.properties 文件应该禁用默认处理程序 -

    spring.mvc.throw-exception-if-no-handler-found=true
    

    然后你可以创建一个类来处理异常 -

    @RestControllerAdvice
    public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler {
        
        @ResponseBody
        @ResponseStatus(HttpStatus.FORBIDDEN)
        @ExceptionHandler(value = {TokenExpiredException.class})
        public ApiResponse handleTokenExpiredException(TokenExpiredException ex, WebRequest request) {
            return new ApiResponse(ex.getMessage());
        }
    }
    

    这里,ApiResponse 是一个代表您的自定义响应消息的类。

    【讨论】:

    • 是的,我目前有一个可以捕获异常的@ControllerAdvice 类。我尝试在过滤器中抛出一个自定义异常类并将其添加到我的异常处理程序中。它没有被抓住。我想我之前尝试过,但也失败了,这就是我尝试另一条路线的原因。我猜 ControllerAdvice 或 RestControllerAdvice 只处理通过控制器的异常,而这个异常是在它到达那个点之前发生的。
    • 是的!我的回答没有用。在这方面还有其他 SO 讨论,这似乎很有希望。我会自己尝试一种方法,稍后再讨论。
    • 我认为这就是我需要的:docs.spring.io/spring-boot/docs/current/reference/htmlsingle/…。它说:To replace the default behavior completely, you can implement ErrorController and register a bean definition of that type or add a bean of type ErrorAttributes to use the existing mechanism but replace the contents.,但我不知道它说“...实现 ErrorController...”之后是什么意思
    猜你喜欢
    • 2016-06-04
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    • 2014-09-15
    • 1970-01-01
    相关资源
    最近更新 更多