Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning custom error in spring security filter

I am working on a Spring Boot & Spring Security application that makes use of JSON Web Tokens.

I have a spring security filter that checks for the presence of an existing JWT and if so, injects a UsernamePasswordAuthenticationToken:

public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {

    @Value("${api.token.header}")
    String tokenHeader;

    @Autowired
    TokenUtility tokenUtility;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

        String incomingToken = httpRequest.getHeader(tokenHeader);

        if (SecurityContextHolder.getContext().getAuthentication() == null && incomingToken != null) {

            UserDetails userDetails = null;

            try {

                userDetails = tokenUtility.validateToken(incomingToken);

            } catch (TokenExpiredException e) {

                throw new ServletException("Token has expired", e);
            }

            if (userDetails != null) {

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
}

This filter is injected as follows:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    EntryPointUnauthorizedHandler unauthorizedHandler;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

        authenticationManagerBuilder
                            .userDetailsService(userDetailsService)
                            .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {

        return super.authenticationManager();
    }

    @Bean
    public AuthenticationTokenFilter authenticationTokenFilter() throws Exception {

        AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
        authenticationTokenFilter.setAuthenticationManager(authenticationManager());

        return authenticationTokenFilter;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            .csrf()
                .disable()
            .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler)
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .anyRequest().authenticated();

        // filter injected here
        httpSecurity.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

If a user passes in a token that has expired, they receive the following error:

{
    "timestamp":1496424964894,
    "status":500,
    "error":"Internal Server Error",
    "exception":"com.app.exceptions.TokenExpiredException",
    "message":"javax.servlet.ServletException: Token has expired",
    "path":"/orders"
}

I know that spring security intercepts the requests before they make it to the controller layer, so I can't use my existing @ControllerAdvice to handle these exceptions.

My question is, how do I customise the error message/object that gets returned here? Elsewhere I use a JSON-serialized POJO to return error messages and I want to be consistent. I also don't want the user to see javax.servlet.ServletException

like image 647
smilin_stan Avatar asked Feb 01 '26 21:02

smilin_stan


1 Answers

First, modify JWTTokenProvider Class to add a custom header to Http Servlet Request using setAttribute() method.

   public boolean validateToken(String token,HttpServletRequest httpServletRequest){
    try {
        Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
        return true;
    }catch (SignatureException ex){
        System.out.println("Invalid JWT Signature");
    }catch (MalformedJwtException ex){
        System.out.println("Invalid JWT token");
    }catch (ExpiredJwtException ex){
        System.out.println("Expired JWT token");
        httpServletRequest.setAttribute("expired",ex.getMessage());
    }catch (UnsupportedJwtException ex){
        System.out.println("Unsupported JWT exception");
    }catch (IllegalArgumentException ex){
        System.out.println("Jwt claims string is empty");
    }
    return false;

}

Then modify commence method in JwtAuthenticationEntryPoint class to check expired header in http servlet request header that we added above.

@Override
public void commence(HttpServletRequest httpServletRequest,
                     HttpServletResponse httpServletResponse,
                     AuthenticationException e) throws IOException, ServletException {

    final String expired = (String) httpServletRequest.getAttribute("expired");
    System.out.println(expired);
    if (expired!=null){
        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
    }else{
        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
    }

}

For more details see this Post. A nice simple solution.

like image 102
Sanjaya Senadheera Avatar answered Feb 03 '26 11:02

Sanjaya Senadheera



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!