如何管理Spring中过滤器中抛出的异常?

时间:2016-01-04 16:34:38

标签: java spring spring-security

我想使用通用方法来管理5xx错误代码,让我们特别说明db在整个spring应用程序中出现故障的情况。我想要一个漂亮的错误json而不是堆栈跟踪。

对于控制器,我有一个@ControllerAdvice类用于不同的异常,这也是捕获db在请求中间停止的情况。但这并不是全部。我也碰巧有一个自定义CorsFilter延长OncePerRequestFilter,当我致电doFilter时,我会收到CannotGetJdbcConnectionException,而@ControllerAdvice则不会对其进行管理。我在线阅读了几件让我更加困惑的事情。

所以我有很多问题:

  • 我是否需要实施自定义过滤器?我找到了ExceptionTranslationFilter,但这只会处理AuthenticationExceptionAccessDeniedException
  • 我想实现自己的HandlerExceptionResolver,但这让我怀疑,我没有任何自定义异常来管理,必须有一个比这更明显的方法。我还尝试添加一个try / catch并调用HandlerExceptionResolver的实现(应该足够好,我的异常没什么特别的)但是这不会在响应中返回任何内容,我得到状态200并且为空身体。

有什么好方法可以解决这个问题吗?感谢

14 个答案:

答案 0 :(得分:55)

所以这就是我所做的:

我阅读了有关过滤器here的基础知识,并且我发现我需要创建一个自定义过滤器,该过滤器将首先位于过滤器链中,并且将有一个try catch来捕获可能在那里发生的所有运行时异常。然后我需要手动创建json并将其放入响应中。

所以这是我的自定义过滤器:

public class ExceptionHandlerFilter extends OncePerRequestFilter {

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } catch (RuntimeException e) {

            // custom error response class used across my project
            ErrorResponse errorResponse = new ErrorResponse(e);

            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.getWriter().write(convertObjectToJson(errorResponse));
    }
}

    public String convertObjectToJson(Object object) throws JsonProcessingException {
        if (object == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(object);
    }
}

然后我在CorsFilter之前的web.xml中添加了它。它的工作原理!

<filter> 
    <filter-name>exceptionHandlerFilter</filter-name> 
    <filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class> 
</filter> 


<filter-mapping> 
    <filter-name>exceptionHandlerFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

<filter> 
    <filter-name>CorsFilter</filter-name> 
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter> 

<filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

答案 1 :(得分:13)

我自己遇到了这个问题,并且我执行了以下步骤,重新使用const letters = {a:'26',b:'25',c:'24',d:'23',e:'22',f:'21',g:'20',h:'19',i:'18',j:'17',k:'16',l:'15',m:'14',n:'13',o:'12',p:'11',q:'10',r:'9',s:'8',t:'7',u:'6',v:'5',w:'4',x:'3',y:'2',z:'1'}; function switcher(num){ var res = Object.keys(letters).find(v => letters[v] == num); return res; } console.log(switcher('26')); console.log(switcher('9'));注释了注册过滤器中抛出的ExceptionController @ControllerAdvise

显然有很多方法可以处理异常,但在我的情况下,我希望异常由我的Exceptions处理,因为我很固执,也因为我不想复制/粘贴相同的代码(即我在ExceptionController中有一些处理/记录代码)。我想返回漂亮的ExceptionController响应,就像其他不是从Filter中抛出的异常一样。

JSON

无论如何,我设法使用了我的{ "status": 400, "message": "some exception thrown when executing the request" } ,我必须做一些额外的工作,如下所示:

<强>步骤

  1. 您有自定义过滤器,可能会也可能不会引发异常
  2. 你有一个Spring控制器使用ExceptionHandler来处理异常,即MyExceptionController
  3. 示例代码

    @ControllerAdvise

    现在是正常情况下处理//sample Filter, to be added in web.xml public MyFilterThatThrowException implements Filter { //Spring Controller annotated with @ControllerAdvise which has handlers //for exceptions private MyExceptionController myExceptionController; @Override public void destroy() { // TODO Auto-generated method stub } @Override public void init(FilterConfig arg0) throws ServletException { //Manually get an instance of MyExceptionController ApplicationContext ctx = WebApplicationContextUtils .getRequiredWebApplicationContext(arg0.getServletContext()); //MyExceptionHanlder is now accessible because I loaded it manually this.myExceptionController = ctx.getBean(MyExceptionController.class); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; try { //code that throws exception } catch(Exception ex) { //MyObject is whatever the output of the below method MyObject errorDTO = myExceptionController.handleMyException(req, ex); //set the response object res.setStatus(errorDTO .getStatus()); res.setContentType("application/json"); //pass down the actual obj that exception handler normally send ObjectMapper mapper = new ObjectMapper(); PrintWriter out = res.getWriter(); out.print(mapper.writeValueAsString(errorDTO )); out.flush(); return; } //proceed normally otherwise chain.doFilter(request, response); } } 的示例Spring Controller(例如,通常不会在Filter级别引发的异常,我们要用于在Filter中抛出的异常)

    Exception

    与希望在过滤器中使用//sample SpringController @ControllerAdvice public class ExceptionController extends ResponseEntityExceptionHandler { //sample handler @ResponseStatus(value = HttpStatus.BAD_REQUEST) @ExceptionHandler(SQLException.class) public @ResponseBody MyObject handleSQLException(HttpServletRequest request, Exception ex){ ErrorDTO response = new ErrorDTO (400, "some exception thrown when " + "executing the request."); return response; } //other handlers } ExceptionController的人共享解决方案。

答案 2 :(得分:9)

如果您想要通用方法,可以在web.xml中定义错误页面:

<error-page>
  <exception-type>java.lang.Throwable</exception-type>
  <location>/500</location>
</error-page>

在Spring MVC中添加映射:

@Controller
public class ErrorController {

    @RequestMapping(value="/500")
    public @ResponseBody String handleException(HttpServletRequest req) {
        // you can get the exception thrown
        Throwable t = (Throwable)req.getAttribute("javax.servlet.error.exception");

        // customize response to what you want
        return "Internal server error.";
    }
}

答案 3 :(得分:8)

所以,这就是我基于上述答案的合并而做的...我们已经用GlobalExceptionHandler注释了@ControllerAdvice,我也希望找到一种方法来重用该代码处理来自过滤器的异常。

我能找到的最简单的解决方案是单独保留异常处理程序,并按如下方式实现错误控制器:

@Controller
public class ErrorControllerImpl implements ErrorController {
  @RequestMapping("/error")
  public void handleError(HttpServletRequest request) throws Throwable {
    if (request.getAttribute("javax.servlet.error.exception") != null) {
      throw (Throwable) request.getAttribute("javax.servlet.error.exception");
    }
  }
}

因此,由异常引起的任何错误首先通过ErrorController并通过从@Controller上下文中重新抛出它们而被重定向到异常处理程序,而任何其他错误(不是直接引起的)通过例外)无需修改即可通过ErrorController

为什么这实际上是一个坏主意?

答案 4 :(得分:5)

这是我的解决方案,可以覆盖默认的Spring Boot /错误处理程序

package com.mypackage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * This controller is vital in order to handle exceptions thrown in Filters.
 */
@RestController
@RequestMapping("/error")
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {

    private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);

    private final ErrorAttributes errorAttributes;

    @Autowired
    public ErrorController(ErrorAttributes errorAttributes) {
        Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
        this.errorAttributes = errorAttributes;
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest aRequest, HttpServletResponse response) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
        Map<String, Object> result =     this.errorAttributes.getErrorAttributes(requestAttributes, false);

        Throwable error = this.errorAttributes.getError(requestAttributes);

        ResponseStatus annotation =     AnnotationUtils.getAnnotation(error.getClass(), ResponseStatus.class);
        HttpStatus statusCode = annotation != null ? annotation.value() : HttpStatus.INTERNAL_SERVER_ERROR;

        result.put("status", statusCode.value());
        result.put("error", statusCode.getReasonPhrase());

        LOGGER.error(result.toString());
        return new ResponseEntity<>(result, statusCode) ;
    }

}

答案 5 :(得分:4)

当您想测试应用程序的状态时,如果出现问题,则返回HTTP错误,我建议使用过滤器。下面的过滤器处理所有HTTP请求。 Spring Boot中使用javax过滤器的最短解决方案。

在实施中可以有各种条件。在我的例子中,applicationManager测试应用程序是否准备就绪。

import ...ApplicationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class SystemIsReadyFilter implements Filter {

    @Autowired
    private ApplicationManager applicationManager;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!applicationManager.isApplicationReady()) {
            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The service is booting.");
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {}
}

答案 6 :(得分:1)

这很奇怪,因为@ControllerAdvice应该有效,你是否正在捕获正确的异常?

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = DataAccessException.class)
    public String defaultErrorHandler(HttpServletResponse response, DataAccessException e) throws Exception {
       response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
       //Json return
    }
}

还尝试在CorsFilter中捕获此异常并发送500错误,类似这样的

@ExceptionHandler(DataAccessException.class)
@ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    //Json return
}

答案 7 :(得分:1)

只是为了补充提供的其他好的答案,我最近也想要一个简单的SpringBoot应用程序中的一个单个错误/异常处理组件,该组件包含可能引发异常的过滤器,而其他异常可能会从控制器引发方法。

幸运的是,似乎没有什么可以阻止您将控制器建议与Spring默认错误处理程序的替代组合在一起,以提供一致的响应有效负载,允许您共享逻辑,检查来自过滤器的异常,捕获特定的服务引发的异常,等等

例如


@ControllerAdvice
@RestController
public class GlobalErrorHandler implements ErrorController {

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ValidationException.class)
  public Error handleValidationException(
      final ValidationException validationException) {
    return new Error("400", "Incorrect params"); // whatever
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(Exception.class)
  public Error handleUnknownException(final Exception exception) {
    return new Error("500", "Unexpected error processing request");
  }

  @RequestMapping("/error")
  public ResponseEntity handleError(final HttpServletRequest request,
      final HttpServletResponse response) {

    Object exception = request.getAttribute("javax.servlet.error.exception");

    // TODO: Logic to inspect exception thrown from Filters...
    return ResponseEntity.badRequest().body(new Error(/* whatever */));
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }

}

答案 8 :(得分:1)

我想提供一个基于the answer of @kopelitsa的解决方案。主要区别在于:

  1. 使用HandlerExceptionResolver重用控制器异常处理。
  2. 在XML配置上使用Java配置

首先,需要确保您有一个类来处理常规RestController / Controller中发生的异常(用@RestControllerAdvice@ControllerAdvice注释的类以及用{注释的方法) {1}})。这可以处理您在控制器中发生的异常。这是使用RestControllerAdvice的示例:

@ExceptionHandler

要在Spring Security过滤器链中重用此行为,您需要定义一个Filter并将其挂钩到您的安全配置中。筛选器需要将异常重定向到上面定义的异常处理。这是一个示例:

@RestControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorDTO processRuntimeException(RuntimeException e) {
        return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.", e);
    }

    private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
        (...)
    }
}

然后需要将创建的过滤器添加到SecurityConfiguration。您需要非常早地将其挂接到链中,因为不会捕获所有先前过滤器的异常。就我而言,将其添加到@Component public class FilterChainExceptionHandler extends OncePerRequestFilter { private final Logger log = LoggerFactory.getLogger(getClass()); @Autowired @Qualifier("handlerExceptionResolver") private HandlerExceptionResolver resolver; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); } catch (RuntimeException e) { log.error("Spring Security Filter Chain RuntimeException:", e); resolver.resolveException(request, response, null, e); } } } 之前是合理的。请参阅默认过滤器链及其顺序in the official docs。这是一个示例:

LogoutFilter

答案 9 :(得分:1)

通读以上答案中建议的不同方法后,我决定使用自定义过滤器处理身份验证异常。我可以使用以下方法使用错误响应类来处理响应状态和代码。

我创建了一个自定义过滤器,并使用addFilterAfter方法修改了我的安全配置,并将其添加到CorsFilter类之后。

@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    //Cast the servlet request and response to HttpServletRequest and HttpServletResponse
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    // Grab the exception from the request attribute
    Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
    //Set response content type to application/json
    httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);

    //check if exception is not null and determine the instance of the exception to further manipulate the status codes and messages of your exception
    if(exception!=null && exception instanceof AuthorizationParameterNotFoundException){
        ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write(convertObjectToJson(errorResponse));
        writer.flush();
        return;
    }
    // If exception instance cannot be determined, then throw a nice exception and desired response code.
    else if(exception!=null){
            ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.write(convertObjectToJson(errorResponse));
            writer.flush();
            return;
        }
        else {
        // proceed with the initial request if no exception is thrown.
            chain.doFilter(httpServletRequest,httpServletResponse);
        }
    }

public String convertObjectToJson(Object object) throws JsonProcessingException {
    if (object == null) {
        return null;
    }
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(object);
}
}

SecurityConfig类

    @Configuration
    public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    AuthFilter authenticationFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(authenticationFilter, CorsFilter.class).csrf().disable()
                .cors(); //........
        return http;
     }
   }

ErrorResponse类

public class ErrorResponse  {
private final String message;
private final String description;

public ErrorResponse(String description, String message) {
    this.message = message;
    this.description = description;
}

public String getMessage() {
    return message;
}

public String getDescription() {
    return description;
}}

答案 10 :(得分:1)

我在 webflux 中遇到了同样的问题,主题是有人希望在那里重用 @ControllerAdvice,您不想在 webfilter 中抛出直接异常或返回单声道错误,但是您想设置响应成为单声道错误。

    public class YourFilter implements WebFilter {


    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
        exchange.getResponse().writeWith(Mono.error(new YouException()));
        return chain.filter(exchange)
    }
}

答案 11 :(得分:1)

在过滤器中,我们没有带有 @ControllerAdvice@RestControllerAdvice 的控件来处理在进行身份验证时可能发生的异常。因为 DispatcherServlet 只有在 Controller 类命中后才会出现。 因此,我们需要执行以下操作。

  1. 我们需要

    HttpServletResponse httpResponse = (HttpServletResponse) response;

"response" 对象,我们可以从 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 实现类的 GenericFilterBean.java 传递它。 2) 我们可以使用下面的实用程序类将我们的错误 JSON 模型或 String 对象写入或打印到 ServletResponse 输出流中。

public static void handleUnAuthorizedError(ServletResponse response,Exception e)
{
    ErrorModel error = null;
    if(e!=null)
        error = new ErrorModel(ErrorCodes.ACCOUNT_UNAUTHORIZED, e.getMessage());
    else
        error = new ErrorModel(ErrorCodes.ACCOUNT_UNAUTHORIZED, ApplicationConstants.UNAUTHORIZED);
    
    JsonUtils jsonUtils = new JsonUtils();
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
    httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    try {
        httpResponse.getOutputStream().println(jsonUtils.convertToJSON(error));
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}


public String convertToJSON(Object inputObj) {
        ObjectMapper objectMapper = new ObjectMapper();
        String orderJson = null;
        try {
            orderJson = objectMapper.writeValueAsString(inputObj);
        }
        catch(Exception e){
            e.printStackTrace();
        }
        return orderJson;
    }

答案 12 :(得分:0)

您可以在catch块内使用以下方法:

response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token")

请注意,您可以使用任何HttpStatus代码和自定义消息。

答案 13 :(得分:-1)

您无需为此创建自定义过滤器。我们通过创建扩展ServletException的自定义异常(从声明中所示的doFilter方法抛出)解决了这一问题。然后由我们的全局错误处理程序捕获并处理这些错误。

编辑:语法