Spring security JWT 过滤器抛出 500 和 HTML 而不是 401 和 json

Posted

技术标签:

【中文标题】Spring security JWT 过滤器抛出 500 和 HTML 而不是 401 和 json【英文标题】:Spring security JWT filter throws 500 and HTML instead of 401 and json 【发布时间】:2020-11-04 06:33:00 【问题描述】:

我一直无法让安全系统正常工作,这个问题已经解决了一半 Spring Boot Security wont ignore certain paths that dont need to be secured 第二个问题是spring忽略了失败的HTTP状态码,总是抛出500。

当 JWT 令牌无效时,我想返回 401 和 json 响应。我不断得到一个 500 和白色标签的 html 页面。 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)
        
    

在我的应用程序类中我也有

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

应用程序中的其他任何地方 ResponseStatusException 都会以正确的代码和 JSON 格式抛出错误,例如,当我抛出异常时,响应将是 HTML 之类的

HTTP 状态 500 – 内部服务器错误 身体 字体系列:Tahoma、Arial、无衬线字体;

    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 状态 500 – 内部服务器错误

类型异常报告

消息 401 UNAUTHORIZED "Expired JWT token"

描述 服务器遇到了一个意外情况,导致它无法完成请求。

异常

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)

注意服务器日志中提供了根本原因的完整堆栈跟踪。

Apache Tomcat/9.0.35

【问题讨论】:

我仍然不明白您为什么要编写自己的过滤器和 oauth 支持。它在 Spring/Spring Boot 中都是开箱即用的。授权服务器,如果你是强迫症,在重写时暂时被弃用(大多数人认为这很愚蠢——重写它,然后弃用旧版本哈哈)。 Imo,仍然没有理由编写自己的与经过实战测试的解决方案。您还可以使用 Keycloak 等。 【参考方案1】:

不要在过滤器中抛出异常,而是这样做

    response.sendsetStatus(HttpServletResponse.SC_UNAUTHORIZED);  
    return;

或者如果你也想要消息

    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;

【讨论】:

这工作正常!但我认为那将是 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);【参考方案2】:

这个问题困扰了我三天左右,感谢@Kavithakaran Kanapathippillai 的评论。

我的做法是这样的

        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)

【讨论】:

【参考方案3】:

如果您正在设置 SecurityWebFilterChain 使用的 ServerAuthenticationEntryPoint(与 @EnableWebFluxSecurity 一起提供),您可以使用它

class  HttpBasicServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint

由于某种原因,当我尝试实现自己的入口点时,我得到了 500 而不是 401。但是,它按预期工作,现在抛出 401。

【讨论】:

以上是关于Spring security JWT 过滤器抛出 500 和 HTML 而不是 401 和 json的主要内容,如果未能解决你的问题,请参考以下文章

spring security 的jwt认证以及原理解析

JWT spring security Authentication过滤器丢失标头信息

Spring Security----JWT详解

社交登录,spring-security-oauth2 和 spring-security-jwt?

Spring Security 结合了 Container Security 和 JWT Token

Spring Security + JJWT 实现 JWT 认证和授权