Обработка исключений для фильтра весной
Я обрабатываю исключения весной, используя @ExceptionHandler. Любое исключение, генерируемое контроллером, перехватывается с использованием метода, аннотированного @ExceptionHandler, и действие предпринимается соответственно. Чтобы избежать написания @exceptionHandler для каждого контроллера, я использую аннотацию @ControllerAdvice.
Все работает нормально, как и ожидалось.
Теперь у меня есть фильтр (да, не перехватчик, чтобы обрабатывать определенные требования), который реализован с использованием DelegatingFilterProxy и ContextLoaderListener.
Когда я выбрасываю то же исключение сверху фильтра, его не поймали так, как это было сделано в случае контроллера. Это непосредственно брошено пользователю.
Что здесь не так?
8 ответов
Фильтры происходят до того, как контроллеры даже разрешены, поэтому исключения, создаваемые фильтрами, не могут быть перехвачены советом контроллера.
Фильтры являются частью сервлета, а не стека 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
}
Поскольку исключение не генерируется контроллером, совет контроллера не перехватит исключение, если вы не предоставите настраиваемый фильтр для делегирования исключения.
Вы можете создать другой фильтр, чтобы делегировать свои исключения совету контроллера. Хитрость заключается в том, чтобы поставить этот вновь созданный фильтр перед всеми другими пользовательскими фильтрами ».
Например:
Создайте новый фильтр, чтобы делегировать ваше исключение
@Составная часть открытый класс FilterExceptionHandler расширяет OncePerRequestFilter { частный статический регистратор logger = LoggerFactory.getLogger(FilterExceptionHandler.class); @Autowired @Qualifier("handlerExceptionResolver") частный преобразователь HandlerExceptionResolver; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) выбрасывает ServletException, IOException { пытаться { filterChain.doFilter(httpServletRequest, httpServletResponse); } catch (Exception ex) { logger.error(«Исключение цепочки фильтров Spring Security: {}», например 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 response = (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));
}
}
Если, как и я, вы застряли с пружиной 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;
}
}
Конечно, вы не забудете зарегистрировать свои бывшие
Затем создайте фильтр, который вызывается перед вашим заданным фильтром (необязательно, все они)
Тогда в этом фильтре
try{
chain.doFilter(request, response);
catch(Exception e){
exceptionResolver(request, response, exceptionHandler, e);
//Make the processing stop here...
return; //just in case
}
Проверьте приведенный ниже фрагмент кода, он работает для меня.
final HttpServletResponseWrapper wrapper = new
HttpServletResponseWrapper((HttpServletResponse) res);
wrapper.sendError(HttpServletResponse.SC_UNAUTHORIZED, "<your error msg>");
res = wrapper.getResponse();
Если вы используете это внутри фильтра, то добавьте оператор возврата, иначе chain.doFilter(req,res)
переопределит это.