John P
John P

Reputation: 1219

Spring security BasicAuthenticationFilter returns 403 instead of 401

I have implemented the JWT authentication and authorization. Everything is working fine, besides the unauthorized scenario

Unauthorized scenario: making a http call to a route without providing a authorization token.

Result: 403 forbidden instead of unauthorized

Here is my code:

@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
    String header = req.getHeader(HEADER_STRING);

    if (header == null || !header.startsWith(TOKEN_PREFIX)) {
        chain.doFilter(req, res);
        return;
    }

    UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(req, res);
}

After the

    if (header == null || !header.startsWith(TOKEN_PREFIX)) {
        chain.doFilter(req, res);
        return;
    } 

Executes, the response is 403

Here is my full class:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {

        String token = request.getHeader(HEADER_STRING);
        if (token != null) {

            // setting the user in the security context
            String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();

            if(user != null){
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }

            return null;
        }

        return null;
    }
}

Remark:

I had the same problem with UsernamePasswordAuthenticationFilter, and I solved it by overriding the default authenticationFailureHandler:

setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());

How can I get the correct 401 response code and body?

Thanks!

Upvotes: 4

Views: 2872

Answers (2)

Perhaps this will help someone encountering similar behavior in using spring boot 3.1.x I had a similar issue and fixed it in my securityFilterChain method. The solution was to add the below line to the filterChain.

.httpBasic(Customizer.withDefaults());

Without HTTP Basic authentication configured, the server won't send a 401 Unauthorized status with a WWW-Authenticate header in response to unauthorized requests; instead, it might simply deny access or redirect to a login page depending on how your security is set up.

Upvotes: 1

If you look at what BasicAuthenticationFilter which you are overriding with JWTAuthorizationFilter does when authentication fails, it calls authenticationEntryPoint.commence(request, response, failed) which sends 401

BasicAuthenticationEntryPoint

    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException {
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
    }

But you have behaviour that overridden and returning null. So instead of that try one of the following:

  • Throw BadCredentialsException where you are returning null
  • Do response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());

Upvotes: 3

Related Questions