Skip to content

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>

Answer

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

  1. 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