Search code examples
javaspringspring-securityjwt

spring security with jwt token returns 401 html page not json


why spring returns 401 html page instead of my custom json api response with error? and what is the best way to fix it (override spring config)

security:

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .httpBasic().disable()
            .csrf().disable()
            .cors().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
            .antMatchers("/v2/api-docs").permitAll()
            .antMatchers("/configuration/ui").permitAll()
            .antMatchers("/swagger-resources/**").permitAll()
            .antMatchers("/configuration/security").permitAll()
            .antMatchers("/swagger-ui.html").permitAll()
            .antMatchers("/swagger-ui/*").permitAll()
            .antMatchers("/webjars/**").permitAll()
            .antMatchers("/v2/**").permitAll()
            .antMatchers(LOGIN_ENDPOINT, REGISTER_ENDPOINT, "/v2/api-docs", "/swagger-ui.html", 
"/v2/swagger-ui.html").permitAll()
            .antMatchers(ADMIN_ENDPOINT).hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .apply(new JwtConfigurer(jwtTokenProvider));
}

Filter:

public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;

public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
    this.jwtTokenProvider = jwtTokenProvider;
}

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

    String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);

    try {
        if (token != null && jwtTokenProvider.isTokenValid(token)) {
            var authentication = jwtTokenProvider.getAuthentication(token);

            if (authentication != null) {
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
    } catch (JwtAuthenticationException e) {
        SecurityContextHolder.clearContext();
        ((HttpServletResponse) res).sendError(e.getHttpStatus().value());
        ((HttpServletResponse) res).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        filterChain.doFilter(req, res);
        throw new JwtAuthenticationException("JWT token is expired or invalid", 
HttpStatus.UNAUTHORIZED);
    }
    filterChain.doFilter(req, res);
}
}

so i implemented custom exception handler and filter, and what i need is this type of error

{
   "errorMessage": "Invalid username or password",
   "validationErrors": null
}

that what i get when login fo example, success case to get token is also working

{
"username": "vovaflex",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ2b3ZhZmxleCIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE2MTk4NjQ2MDksImV4cCI6MTYxOTg2ODIwOX0.Oc7AG5ZncfAT3QB8l0mkYDkBr5ZjBfJ2MxLe3M12DF8",
"roles": [
    {
        "id": 1,
        "created": "2021-04-03T18:14:51.891+00:00",
        "updated": "2021-04-03T18:14:51.891+00:00",
        "status": "ACTIVE",
        "name": "ROLE_USER",
        "users": null
    }
]
}

but whey for example token is not correct or expired or so, i get 401 unauthorized (correct) but instead of json with message that i set up i get full html page with error

<!doctype html>
<html lang="en">
<head>
    <title>HTTP Status 401 – Unauthorized</title>
</head>

<body>
    <h1>HTTP Status 401 – Unauthorized</h1>
</body>
</html>

Solution

  • For custom JSON error block, you need to do two things

    Implement the AuthenticationEntryPoint.

    public class AuthExceptionEntryPoint implements AuthenticationEntryPoint
    {
        @Override
        public void commence(
            HttpServletRequest request, 
            HttpServletResponse response, 
            AuthenticationException arg2
        ) throws IOException, ServletException {
            
        }
    }
    

    and add that reference in the websecurity settings configure(HttpSecurity http)

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
    {   
        @Override
        public void configure(HttpSecurity http) throws Exception
        {
            http.exceptionHandling().authenticationEntryPoint(new AuthExceptionEntryPoint()) ;
    
        }
    }
    

    check these two resources for more details

    JSON custom authentication

    JSON custom error response