Search code examples
springspring-bootspring-security

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


Solution

  • 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;