我正在使用 @ExceptionHandler 在 spring 中处理异常。使用 @ExceptionHandler 注释的方法捕获控制器抛出的任何异常,并采取相应的操作。为了避免为每个控制器编写@exceptionHandler,我使用@ControllerAdvice注释。
一切都按预期进行。
现在我有一个过滤器(是的,不是拦截器,用于处理某些要求),它是使用 DelegatingFilterProxy 和 ContextLoaderListener 实现的。
当我从上面的过滤器抛出相同的异常时,它没有像在控制器情况下完成的那样被捕获。直接扔给用户。
这里出了什么问题?
过滤器甚至在控制器被解析之前发生,因此控制器建议无法捕获从过滤器抛出的异常。
过滤器是 servlet 的一部分,而不是真正的 MVC 堆栈。
由于异常不是从控制器引发的,而是从过滤器引发的,@ControllerAdvice 不会捕获它。
所以,我到处寻找后找到的最好的解决方案是为这个内部错误创建一个过滤器:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (JwtException e) {
setErrorResponse(HttpStatus.BAD_REQUEST, response, e);
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, response, e);
}
}
public void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable ex){
response.setStatus(status.value());
response.setContentType("application/json");
// A class used for errors
ApiError apiError = new ApiError(status, ex);
try {
String json = apiError.convertToJson();
System.out.println(json);
response.getWriter().write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
将其添加到您的配置中,我正在使用 WebSecurityConfigurerAdapter 实现:
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// Custom Exception Filter for filter
httpSecurity
.addFilterBefore(exceptionHandlerFilterBean(), JwtAuthenticationTokenFilter.class);
错误类别:
public class ApiError {
private HttpStatus status;
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String debugMessage;
private ApiError() {
timestamp = LocalDateTime.now();
}
public ApiError(HttpStatus status) {
this();
this.status = status;
}
public ApiError(HttpStatus status, Throwable ex) {
this();
this.status = status;
this.message = "Unexpected error";
this.debugMessage = ex.getLocalizedMessage();
}
public ApiError(HttpStatus status, String message, Throwable ex) {
this();
this.status = status;
this.message = message;
this.debugMessage = ex.getLocalizedMessage();
}
public String convertToJson() throws JsonProcessingException {
if (this == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper.writeValueAsString(this);
}
//Setters and getters
}
由于异常不是由控制器抛出的,因此控制器建议不会捕获异常,除非您提供自定义过滤器来委托异常。
您可以创建另一个过滤器将异常委托给控制器建议。诀窍是在所有其他自定义过滤器之前提供这个新创建的过滤器。'
例如:
创建一个新的过滤器来委托您的异常
@Component
public class FilterExceptionHandler extends OncePerRequestFilter {
private static Logger logger = LoggerFactory.getLogger(FilterExceptionHandler.class);
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (Exception ex) {
logger.error("Spring Security filter chain exception : {}", ex.getMessage());
resolver.resolveException(httpServletRequest, httpServletResponse, null, ex);
}
}}
如果需要,创建自定义异常。就我而言,我正在创建一个异常 JukeBoxUnAuthorizedException
public class JukeBoxUnauthorizedException extends RuntimeException {
private static final long serialVersionUID = 3231324329208948384L;
public JukeBoxUnauthorizedException() {
super();
}
public JukeBoxUnauthorizedException(String message) {
super(message);
}
public JukeBoxUnauthorizedException(String message, Throwable cause) {
super(message, cause);
}
}
创建一个控制器建议来处理此异常
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(value = {JukeBoxUnauthorizedException.class})
public ResponseEntity<JukeboxResponse> handleUnAuthorizedException(JukeBoxUnauthorizedException exception) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse(exception.getMessage()));
}
}
在 SecurityConfigurtion 中添加异常委托过滤器。即在
configure(HttpSecurity http)
方法中。请注意,异常委托过滤器应位于层次结构的顶部。它应该位于所有自定义过滤器之前
http.addFilterBefore(exceptionHandlerFilter, AuthTokenFilter.class);
大概您想设置 HTTP 状态代码作为过滤器中抛出异常的结果?如果是这样,只需按如下方式设置状态即可:
HttpServletResponse 响应 = (HttpServletResponse) res; response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
这就是我在过滤器类中所做的抛出错误的操作:
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (req.getHeader("Content-Type") == null) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Required headers not specified in the request");
}
chain.doFilter(request, response);
}
我使用rest api构建了我的应用程序,所以我通过在可能抛出异常的过滤器中捕获它然后写回一些东西来解决这个问题。请记住,必须包含
filterChain.doFilter(request, response);
。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// something may throw an exception
filterChain.doFilter(request, response);
} catch (Exception e) {
// ResponseWrapper is a customized class
ResponseWrapper responseWrapper = new ResponseWrapper().fail().msg(e.getMessage());
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JSON.toJSONString(responseWrapper));
}
}
检查下面的代码片段,它对我有用。
final HttpServletResponseWrapper wrapper = new
HttpServletResponseWrapper((HttpServletResponse) res);
wrapper.sendError(HttpServletResponse.SC_UNAUTHORIZED, "<your error msg>");
res = wrapper.getResponse();
如果您在过滤器内使用它,请添加一个 return 语句,否则
chain.doFilter(req,res)
将覆盖它。
如果,像我一样,你被 spring 3.1 困住了(仅落后 0.1 版本
@ControllerAdvice
),你可以尝试我刚刚想出的这个解决方案。
那么,您听说过异常解析器,对吗?如果没有,请阅读此处:
@Component
public class RestExceptionResolver extends ExceptionHandlerExceptionResolver {
@Autowired
//If you have multiple handlers make this a list of handlers
private RestExceptionHandler restExceptionHandler;
/**
* This resolver needs to be injected because it is the easiest (maybe only) way of getting the configured MessageConverters
*/
@Resource
private ExceptionHandlerExceptionResolver defaultResolver;
@PostConstruct
public void afterPropertiesSet() {
setMessageConverters(defaultResolver.getMessageConverters());
setOrder(2); // The annotation @Order(2) does not work for this type of component
super.afterPropertiesSet();
}
@Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
ExceptionHandlerMethodResolver methodResolver = new ExceptionHandlerMethodResolver(restExceptionHandler.getClass());
Method method = methodResolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(restExceptionHandler, method);
}
return null;
}
public void setRestExceptionHandler(RestExceptionHandler restExceptionHandler) {
this.restExceptionHandler = restExceptionHandler;
}
public void setDefaultResolver(ExceptionHandlerExceptionResolver defaultResolver) {
this.defaultResolver = defaultResolver;
}
}
然后示例处理程序将如下所示
@Component
public class RestExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public Map<String, Object> handleException(ResourceNotFoundException e, HttpServletResponse response) {
Map<String, Object> error = new HashMap<>();
error.put("error", e.getMessage());
error.put("resource", e.getResource());
return error;
}
}
当然你不会忘记登记你的bes
然后创建一个在您想要的过滤器(可选全部)之前调用的过滤器
然后在那个过滤器中
try{
chain.doFilter(request, response);
catch(Exception e){
exceptionResolver(request, response, exceptionHandler, e);
//Make the processing stop here...
return; //just in case
}
我遇到了同样的问题,当我的 JwtFilter 无法验证 jwt 时,它会导致 403 甚至 500,而我想成为 401。我试图在我的全局
@ControllerAdvice
异常处理程序中捕获 BadCredentialsException,但没有结果。
因此,正确的方法是引入一个
response.getWriter()
如果您的案例是关于 JwtFilter 中发生的与 jwt 相关的异常JwtAuthEntryPoint
并将其绑定到您的网络安全配置中,如下所示:
@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// Respond with 401 Unauthorized error
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing JWT token");
}
}