Spring:如何让过滤器抛出自定义异常?

Posted

技术标签:

【中文标题】Spring:如何让过滤器抛出自定义异常?【英文标题】:Spring: How to make a filter throw a custom exception? 【发布时间】:2017-10-17 20:26:33 【问题描述】:

我创建了一个过滤器,用于验证 JWT 令牌的每个请求标头:

public class JWTAuthenticationFilter extends GenericFilterBean 

    private UserDetailsService customUserDetailsService;
    private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
    private final static UrlPathHelper urlPathHelper = new UrlPathHelper();

    public JWTAuthenticationFilter(UserDetailsService customUserDetailsService) 
        this.customUserDetailsService = customUserDetailsService;
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException 
        Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        if (authentication == null) 
            logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
        
        filterChain.doFilter(request, response);
    


我想抛出一个自定义异常,该异常返回响应:

@ResponseStatus(value=HttpStatus.SOMECODE, reason="There was an issue with the provided authentacion information")  // 409
public class CustomAuthenticationException extends RuntimeException 

    private static final long serialVersionUID = 6699623945573914987L;


我应该怎么做?捕获过滤器抛出的此类异常的最佳设计是什么? 是否有任何一种由 Spring 安全提供的异常处理机制,我可以使用并一次性捕获所有内容? 有没有其他方法可以在过滤器中抛出自定义异常?

注意:还有另一个问题here,其接受的答案没有回答我的问题。我想在到达任何控制器之前返回响应。

我要处理的错误情况: 1. 客户端为 Authorization 标头发送一个空值。 2. 客户端发送格式错误的令牌

在这两种情况下,我都会收到带有500 HTTP 状态代码的响应。我想找回4XX 代码。

【问题讨论】:

我猜你没有把我的问题读到最后。在最后一段中,我明确给出了该帖子的链接,并说明了为什么该问题有所不同。 但是应该是AuthenticationException来执行失败处理。我在我的问题中添加了一些示例案例。 您可以通过添加自定义成功和失败处理程序来做到这一点,并从 AbstractAuthenticationProcessingFilter 扩展您的自定义过滤器 @ArianHosseinzadeh 在您的情况下可以抛出哪种类型的异常? IllegalArgumentException, MalformedJwtException, SignatureException 是一些例子。 【参考方案1】:

看看@ControllerAdvice

这是我项目中的一个示例。

@ControllerAdvice
@RestController
public class GlobalExceptionHandler 

    private final Logger log = Logger.getLogger(this.getClass().getSimpleName());

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = RuntimeException.class)
    public Response handleBaseException(RuntimeException e) 
        log.error("Error", e);
        Error error = new Error(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name());
        return Response.status(HttpStatus.BAD_REQUEST.value()).error(error, null).build();
    

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public Response handleNoHandlerFoundException(Exception e) 
        log.error("Error", e);
        Error error = new Error(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.name());
        return Response.status(HttpStatus.NOT_FOUND.value()).error(error, null).build();
    

    @ExceptionHandler(value = AuthenticationCredentialsNotFoundException.class)
    public Response handleException(AuthenticationCredentialsNotFoundException e)      
        log.error("Error", e);
        Error error = new Error(ErrorCodes.INVALID_CREDENTIALS_CODE, ErrorCodes.INVALID_CREDENTIALS_MSG);
        return Response.status(ErrorCodes.INVALID_CREDENTIALS_CODE).error(error, null).build();
    

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(value = UnauthorisedException.class)
    public Response handleNotAuthorizedExceptionException(UnauthorisedException e)         
//      log.error("Error", e);
        return Response.unauthorized().build();
    

    @ExceptionHandler(value = Exception.class)
    public String handleException(Exception e) 
        log.error("Error", e);
        return e.getClass().getName() + " 14" + e.getMessage();
    



编辑

相信你可以在response.sendError里面做Filter方法。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException 
    Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    if (authentication == null) 
        logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication.");
        setUnauthorizedResponse(response);
        return;
    
    filterChain.doFilter(request, response);


public void setUnauthorizedResponse(HttpServletResponse response) 
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType("application/json");
    Response unAuthorizedResponse = Response.unauthorized().build();
    try 
        PrintWriter out = response.getWriter();
        out.println(unAuthorizedResponse.toJsonString());
     catch (IOException e) 
        log.error("Error", e);
    

【讨论】:

控制器通知只能处理控制器和服务抛出的异常 我编辑了我的问题并添加了两个示例案例。例如 MalformedJwtExceptionIllegalArgumentException,其中 JWT 字符串为空。我不能为IllegalArgumentException 提供全局异常处理程序。这种异常并不总是认证案例。它可能与其他错误有关。 谢谢你的回答,我会把EDIT移到最上面,打电话Response.unauthorized()时我也会得到The method unauthorized() is undefined 响应是自己的类自定义类。您可以使用自己的。【参考方案2】:

我在使用 JWT 令牌时遇到了同样的问题,并在 question 上发布了解决方案,因为那里的问题类似(他遇到了过滤器异常问题)

【讨论】:

【参考方案3】:

免责声明:这不是对所提出问题的回答,而是对 Arian 所问问题的后续回答。

如上所述,请查看如何在 spring 容器允许我们访问 bean 之前启动的地方自动装配。

我在这里自动装配我的 BlacklistJwtRepo

if (blacklistJwtRepo == null)  //Lazy Load because filter
        ServletContext servletContext = req.getServletContext();
        WebApplicationContext webApplicationContext =  WebApplicationContextUtils.getWebApplicationContext(servletContext);
        blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
    

这是我获取 req 对象的地方 -

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
    HttpServletRequest req = (HttpServletRequest) request;

最终代码看起来像 -

    @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException 
    HttpServletRequest req = (HttpServletRequest) request;      
    System.out.println("blacklistJwtRepo : " + blacklistJwtRepo);
    //till here the autowired repo (blacklistJwtRepo) is null
    if (blacklistJwtRepo == null)  //Lazy Load because filter
        ServletContext servletContext = req.getServletContext();
        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
    

【讨论】:

以上是关于Spring:如何让过滤器抛出自定义异常?的主要内容,如果未能解决你的问题,请参考以下文章

当 Spring Boot 中使用 Hibernate Validator 的 Bean 验证失败时,如何抛出自定义异常?

在多层spring项目中抛出自定义异常

未抛出 spring webflux 的自定义异常

无法为 JWT CustomClaimVerifier 抛出自定义异常消息

pyhton抛出自定义的异常

Spring Boot @ControllerAdvice 部分工作,不适用于自定义异常