6

I created a filter which authenticate each request header for JWT token:

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

}

I want to throw a custom exception, and that exception returns a 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;

}

How should I do this ? What is the best design to catch such exception thrown by filters ? Is there any kind of exception handling mechanism provided by the Spring security that I can use and catch everythin in one point ? Is there any other way to throw custom exceptions in a filter ?

Note: there is another question here which its accepted answer doesn't answer my question. I want to return a response before getting to any controller.

Error cases I want to handle: 1. Client sends an empty value for the Authorization header. 2. Client sends a malformed token

In both cases I get a response with 500 HTTP status code. I want to get 4XX code back.

Community
  • 1
  • 1
Arian
  • 7,397
  • 21
  • 89
  • 177
  • I guess you didn't read my question to the end. In the last paragraph I explicitly gave a link to that post and said why that question is different. – Arian May 18 '17 at 07:04
  • But it should be `AuthenticationException` to execute failure handler. I added a few example cases to my question. – Arian May 18 '17 at 07:11
  • You can do this by adding your custom success & failure handlers, and extend your custom filter from AbstractAuthenticationProcessingFilter – Afridi May 18 '17 at 07:11
  • @ArianHosseinzadeh which type of exceptions can be thrown in your case? – Afridi May 18 '17 at 07:13
  • `IllegalArgumentException`, `MalformedJwtException`, `SignatureException` are some examples. – Arian May 18 '17 at 07:14
  • I think all these exceptions should be converted into one generic exception – Afridi May 18 '17 at 07:16
  • I'm going to try extending AbstractAuthenticationProcessingFilter. will let you know. – Arian May 18 '17 at 07:17
  • I pass in `customUserDetailsService` to the current `JWTAuthenticationFilter`. I pass it to `AuthenticationService.getAuthentication` which itself uses it to load the user name (parsed from the token) from the db and get the password to create the authentication token (this happens for each request). I cannot have an argument for `customUserDetailsService` in the constructor for a class that extends `AbstractAuthenticationProcessingFilter`. – Arian May 18 '17 at 07:40
  • @ArianHosseinzadeh can't you Autowire your customUserDetailsService in your custom filter? or just create getter & setter for customUserDetailsService in your custom class – Afridi May 18 '17 at 07:46
  • no, it's a filter. and filter is not a @component. therefore autowiring in it doesn't work. let me take another look to see if I can add a getter, setter for it. – Arian May 18 '17 at 07:48
  • autowiring is working for filter, but you have to create its bean first. Anyway first check whether exception handling working fine or not in filter – Afridi May 18 '17 at 07:55
  • My `JWTAuthenticationFilter` now extends `AbstractAuthenticationProcessingFilter`. There are a few issues. The first issue is that `attemptAuthentication`, `successfulAuthentication` and `unsuccessfulAuthentication` don't get hit, but `doFilter` gets hit. I may ask it in another question. But anything from top of your head ? – Arian May 18 '17 at 08:59
  • let me give you answer – Afridi May 18 '17 at 09:05
  • http://stackoverflow.com/questions/44043348/abstractauthenticationprocessingfilter-dofilter-gets-hit-but-attemptauthentic – Arian May 18 '17 at 09:07
  • @Arian Hi Arian- you can autowire in your custom filters as well (although they are not components ) Please find my answer below to see how you can autowire in filters/or places which are not component – Shubham Arya Jul 07 '21 at 06:22
  • I have used a very simple approach the actual answer is [here](https://stackoverflow.com/a/76481632/6221192). – AmeeQ Jun 15 '23 at 11:16

3 Answers3

4

Take a look at @ControllerAdvice

Here's an example from my project.

@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();
    }


}

Edit

I believe you can response.sendError inside do Filter method.

@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);
    }
}
Drunken Daddy
  • 7,326
  • 14
  • 70
  • 104
  • 7
    Controller advice can only handle exception thrown by controller & services – Afridi May 18 '17 at 07:05
  • I edited my question and added two example cases. For example `MalformedJwtException` and `IllegalArgumentException` where JWT string is empty. I can not have a global exception handler for `IllegalArgumentException`. It's not always authentication case for such exception. It could be related to other errors. – Arian May 18 '17 at 07:09
  • Thank you for answering, I would move the EDIT to the top,and I also get `The method unauthorized() is undefined` when calling `Response.unauthorized()` – Ori Marko Apr 01 '19 at 12:34
  • Response is own class custom class. You may use your own. – Drunken Daddy Apr 02 '19 at 05:38
0

I had the same issue with JWT tokens and posted the solution on this question, since the issue there was similar (he had trouble with filter exceptions)

Richard
  • 719
  • 10
  • 11
0

Disclaimer: This is not the answer to the question asked, but this is a followup answer to the problem which Arian was asking.

As commented above, please see how you can autowire in places which are launched before spring container gives us access to beans.

Here I am autowiring my BlacklistJwtRepo

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

This is where I am getting hold of the req object -

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

Final code looks like -

    @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);
    }
Shubham Arya
  • 585
  • 5
  • 18