Brian
Brian

Reputation: 4408

Spring security JWT filter throws 500 and HTML instead of 401 and json

I've been having trouble getting security working correctly, half of the problem was fixed with this Spring Boot Security wont ignore certain paths that dont need to be secured Second problem is spring is ignoring the HTTP status code on failure and always throws a 500.

When the JWT token is invalid I want to return a 401 and a json response. I keep getting a 500 and the white label html page. JwtFilter

class JwtFilter(private val tokenService: TokenService) : GenericFilterBean() {

    override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {

        val request = req as HttpServletRequest
        val response = res as HttpServletResponse

        val httpRequest = request as HttpServletRequest
        val path = httpRequest.servletPath.toString().substring(0, 12)
        if (path == "/api/v1/auth") {
            chain.doFilter(req, res)
            return
        } else {
            val token = TokenUtil.extractToken(request as HttpServletRequest)

            if (token != null && token.isNotEmpty()) {
                try {
                    tokenService.getClaims(token)
                } catch (e: SignatureException) {
                    throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT Signature")
                } catch (e: MalformedJwtException) {
                    throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT token")
                } catch (e: ExpiredJwtException) {
                    throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Expired JWT token")
                } catch (e: UnsupportedJwtException) {
                    throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unsupported JWT exception")
                } catch (e: IllegalArgumentException) {
                    throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Jwt claims string is empty")
                }
            } else {
                throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing auth token")
            }
            chain.doFilter(req, res)
        }
    }
}

In my application class too I also have

@SpringBootApplication(exclude = [ErrorMvcAutoConfiguration::class])

Everywhere else in the application ResponseStatusException throws an error with the correct code and in JSON format, here for example when I throw an exception the response will be HTML like

<!doctype html>

HTTP Status 500 – Internal Server Error body { font-family: Tahoma, Arial, sans-serif; }
    h1,
    h2,
    h3,
    b {
        color: white;
        background-color: #525D76;
    }

    h1 {
        font-size: 22px;
    }

    h2 {
        font-size: 16px;
    }

    h3 {
        font-size: 14px;
    }

    p {
        font-size: 12px;
    }

    a {
        color: black;
    }

    .line {
        height: 1px;
        background-color: #525D76;
        border: none;
    }
</style>

HTTP Status 500 – Internal Server Error

Type Exception Report

Message 401 UNAUTHORIZED "Expired JWT token"

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.web.server.ResponseStatusException: 401 UNAUTHORIZED "Expired JWT token"
    events.slap.app.web.security.JwtFilter.doFilter(JwtFilter.kt:40)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
    org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
    org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)

Note The full stack trace of the root cause is available in the server logs.

Apache Tomcat/9.0.35

Upvotes: 13

Views: 9665

Answers (3)

bluecheese
bluecheese

Reputation: 71

If you are setting up an ServerAuthenticationEntryPoint that is used by SecurityWebFilterChain (comes with @EnableWebFluxSecurity) you can use this

class  HttpBasicServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint

For some reason, I got 500 thrown instead of 401 when I tried to implement my own EntryPoint. It however works as expected and throws 401 now.

Upvotes: 0

W Kenny
W Kenny

Reputation: 2069

This problem has been bothered me around three-day and thanks @Kavithakaran Kanapathippillai 's comment.

Following is the way I did

        if (token != null && token.isNotEmpty()) {
            String msg = new String();
            try {
                tokenService.getClaims(token)
            } catch (SignatureException ex) {
            msg = "Invalid JWT signature";
            } catch (MalformedJwtException ex) {
            msg = "Invalid JWT token";
            } catch (ExpiredJwtException ex) {
            msg = "Expired JWT token";
            } catch (UnsupportedJwtException ex) {
            msg = "Unsupported JWT token";
            } catch (IllegalArgumentException ex) {
            msg = "JWT claims string is empty.";
            }
        if (msg.isNotEmpty()) {
            StringBuilder sb = new StringBuilder();
            sb.append("{ ");
            sb.append("\"error\": \"Unauthorized\",");
            sb.append("\"message\": \"Invalid Token.\",");
            sb.append("\"path\": \"")
            .append(request.getRequestURL())
            .append("\"");
            sb.append("} ");
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(sb.toString());
            return;
        }
        chain.doFilter(req, res)

Upvotes: 1

Instead of throwing exceptions in the filter, do this

    response.sendsetStatus(HttpServletResponse.SC_UNAUTHORIZED);  
    return;

or if you want message as well

    StringBuilder sb = new StringBuilder();
    sb.append("{ ");
    sb.append("\"error\": \"Unauthorized\" ");
    sb.append("\"message\": \"Unauthorized\"");<--- your message here
    sb.append("\"path\": \"")
      .append(request.getRequestURL())
      .append("\"");
    sb.append("} ");

    response.setContentType("application/json");
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  
    response.getWriter().write(sb.toString());
    return;

Upvotes: 11

Related Questions