使用@ControllerAdvice使简单的servlet过滤器工作

时间:2015-05-19 20:06:23

标签: spring filter exception-handling spring-security spring-boot

我有一个简单的过滤器,只是为了检查一个请求是否包含一个带有静态密钥的特殊标头 - 没有用户身份验证 - 只是为了保护端点。如果密钥不匹配,则抛出AccessForbiddenException,然后将其映射到带有@ControllerAdvice注释的类的响应。但是我不能让它发挥作用。我的@ExceptionHandler未被调用。

ClientKeyFilter

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller

import javax.servlet.*
import javax.servlet.http.HttpServletRequest

@Controller //I know that @Component might be here
public class ClientKeyFilter implements Filter {

  @Value('${CLIENT_KEY}')
  String clientKey

  public void init(FilterConfig filterConfig) {}

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
    req = (HttpServletRequest) req
    def reqClientKey = req.getHeader('Client-Key')
    if (!clientKey.equals(reqClientKey)) {
      throw new AccessForbiddenException('Invalid API key')
    }
    chain.doFilter(req, res)
  }

  public void destroy() {}
}

AccessForbiddenException

public class AccessForbiddenException extends RuntimeException {
  AccessForbiddenException(String message) {
    super(message)
  }
}

ExceptionController

@ControllerAdvice
class ExceptionController {
  static final Logger logger = LoggerFactory.getLogger(ExceptionController)

  @ExceptionHandler(AccessForbiddenException)
  public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
    logger.error('Caught exception.', e)
    return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
  }
}

我哪里错了?简单的servlet过滤器可以使用spring-boot的异常映射吗?

3 个答案:

答案 0 :(得分:5)

由java servlet规范Filter指定,在调用Servlet之前始终执行。现在@ControllerAdvice仅对在DispatcherServlet内执行的控制器有用。因此,使用Filter并期望@ControllerAdvice或在此情况下@ExceptionHandler将被调用不会发生。

您需要在过滤器中放置相同的逻辑(用于编写JSON响应)或者使用trigger()来代替过滤器进行此检查。最简单的方法是扩展HandlerInterceptor,然后覆盖并实现preHandle方法,并将过滤器中的逻辑放入该方法中。

public class ClientKeyInterceptor extends HandlerInterceptorAdapter {

    @Value('${CLIENT_KEY}')
    String clientKey

    @Override
    public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
        String reqClientKey = req.getHeader('Client-Key')
        if (!clientKey.equals(reqClientKey)) {
          throw new AccessForbiddenException('Invalid API key')
        }
        return true;
    }

}

答案 1 :(得分:4)

您无法使用@ControllerAdvice,因为某些控制器出现异常时会调用它,但您的ClientKeyFilter不是@Controller

您应该使用@Controller替换@Component注释,并将响应正文和状态设置为:

@Component
public class ClientKeyFilter implements Filter {

    @Value('${CLIENT_KEY}')
    String clientKey

    public void init(FilterConfig filterConfig) {
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String reqClientKey = request.getHeader("Client-Key");

        if (!clientKey.equals(reqClientKey)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
            return;
        }

        chain.doFilter(req, res);
    }

    public void destroy() {
    }
}

答案 2 :(得分:0)

Java类中的Servlet过滤器用于以下目的:

  • 在客户端访问后端资源之前检查它们的请求。
  • 要先检查服务器的响应,然后再将其发送回客户端。

从Filter抛出的异常可能不会被 @ControllerAdvice 捕获,因为in可能无法到达DispatcherServlet。我正在按以下方式处理我的项目:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        String token = null;
        String bearerToken = request.getHeader("Authorization");

        if (bearerToken != null && (bearerToken.contains("Bearer "))) {
            if (bearerToken.startsWith("Bearer "))
                token = bearerToken.substring(7, bearerToken.length());
            try {
                AuthenticationInfo authInfo = TokenHandler.validateToken(token);
                logger.debug("Found id:{}", authInfo.getId());
                authInfo.uri = request.getRequestURI();
                
                AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
                SecurityContextHolder.getContext().setAuthentication(persistentBean);
                logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
                
            } catch (AuthenticationException authException) {
                logger.error("User Unauthorized: Invalid token provided");
                raiseException(request, response);
                return;
            } catch (Exception e) {
                raiseException(request, response);
                return;
            }

//包装错误响应

private void raiseException(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
    apiError.setMessage("User Unauthorized: Invalid token provided");
    apiError.setPath(request.getRequestURI());
    byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
    response.getOutputStream().write(body);
}

// ApiError类

public class ApiError {
    // 4xx and 5xx
    private HttpStatus status;

    // holds a user-friendly message about the error.
    private String message;

    // holds a system message describing the error in more detail.
    private String debugMessage;

    // returns the part of this request's URL
    private String path;

    public ApiError(HttpStatus status) {
      this();
      this.status = status;
    }
   //setter and getters