Make simple servlet filter work with @ControllerAdvice

后端 未结 3 1957
野性不改
野性不改 2021-01-04 05:32

I\'ve a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessFo

相关标签:
3条回答
  • 2021-01-04 06:19

    As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a @ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a @ControllerAdvice or in this case the @ExceptionHandler, to be invoked isn't going to happen.

    You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.

    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;
        }
    
    }
    
    0 讨论(0)
  • Servlet Filters in Java classes are used for the following purposes:

    • To check requests from client before they access resources at backend.
    • To check responses from server before sent back to the client.

    Exception throw from Filter may not be catch by @ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:

    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;
                }
    

    // Wrapping the error response

    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 class

    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
    
    0 讨论(0)
  • 2021-01-04 06:31

    You can't use @ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a @Controller.

    You should replace the @Controller annotation with the @Component and just set response body and status like this:

    @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() {
        }
    }
    
    0 讨论(0)
提交回复
热议问题