当前位置: 首页 > 知识库问答 >
问题:

让简单的servlet过滤器与@ControllerAdvice一起工作

晏鸿畅
2023-03-14

我有一个简单的过滤器,用于检查请求是否包含一个带有静态密钥的特殊头(无用户身份验证),以保护endpoint。这个想法是,如果键不匹配,抛出一个AccessForbiddenException,然后映射到带有@ControllerAdvice注释的类的响应。但是我不能让它工作。我的@ExceptionHandler未被调用。

客户端

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() {}
}

访问禁止例外

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

例外控制器

@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个答案

夹谷承安
2023-03-14

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

  • 在客户端访问后端资源之前检查客户端的请求
  • 在发送回客户端之前检查服务器的响应

@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
微生嘉祥
2023-03-14

您不能使用@Controlller建议,因为它会在某些控制器中出现异常时被调用,但是您的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() {
    }
}
龙星渊
2023-03-14

根据java servlet规范过滤器的规定,总是在调用servlet之前执行。现在,@ControllerAdvice只对在DispatcherServlet中执行的控制器有用。因此,使用过滤器并期望调用@ControllerAdvice,或者在本例中调用@ExceptionHandler,是不会发生的。

您需要将相同的逻辑放入过滤器中(用于编写JSON响应),或者使用执行此检查的HandlerInterceptor代替过滤器。最简单的方法是扩展HandlerInterceptorAdapter,然后覆盖并实现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;
    }

}

 类似资料:
  • 问题内容: Servlet和Filter有什么区别?您建议使用什么来授权页面? 问题答案: 当你要根据特定条件过滤和/或修改请求时,请使用。使用时要控制,预处理和/或后处理请求。 在Java EE的教程中提到有关筛选如下: 筛选器是可以转换请求或响应的标头和内容(或两者)的对象。过滤器与Web组件的不同之处在于,过滤器本身通常不会创建响应。相反,过滤器提供的功能可以“附加”到任何类型的Web资源。

  • 我正在尝试获得一个LocalStack/LocalStack的本地docker实例来使用Node.js aws-sdk库。但我可以得到一个简单的createTopic工作在一个sns客户端。 我已经用这个命令启动了docker映像 docker Run-d-p 4567-4583:4567-4583 LocalStack/LocalStack 我正在运行的代码.... 返回的错误.... [AWS

  • 问题内容: 现在可以将Spring中的 s配置为仅使用来在某些URL上调用。 Servlet过滤器可以实现相同的功能(日志记录,安全性等)。那么应该使用哪一个呢? 我认为使用Interceptor,可以使用对象与模型一起使用,因此它具有更多的优势。谁能提出过滤器或拦截器比其他方法更具优势的方案? 问题答案: 该接口的JavaDoc本身有两个段落讨论这个问题: HandlerInterceptor基

  • 我有一个使用的自定义记录器,我希望它总是在最后一次运行,这样无论控制器返回什么响应,它都将被记录到数据库中(所以我在这个方面放了一个)。我还使用编写了一个错误处理程序,它处理所有意外的异常并返回,并带有自定义响应体,我希望日志记录器也记录它,因此我在它上添加了,但是看起来注释并没有在Spring方面和Spring ControllerAdvision之间安排顺序,那么如何让错误处理程序始终在日志记

  • 因此,如果我不能重写字符串作为它的最终结果(因此阻止我重写它的compareTo()方法来调用compareToIgnoreCase()),那么还有其他方法可以实现吗? 任何帮助都是非常感谢的。

  • 我已经成功地在我的Windows机器上安装了gnuradio,并尝试将随附的python环境(Python 2.7)与PyCharm v2018集成。我创建了一个新项目,并为包添加了一个用户定义的路径,以指向所有gnuradio库的位置(C:\Program Files\GNURadio-3.7\lib\site-包)。 在Pycharm可以毫无怨言地看到所有gnuradio包的意义上,一切似乎都