我为我的Spring启动应用程序实现了JWT身份验证。总体而言,它的工作方式如下:

  • 客户端将用户名,密码发送到登录端点。
  • 服务器检查提供的凭据是否有效。
  • 如果否,则将返回错误
  • 如果是,它将返回一个 token ,该 token 实际上包括
  • 客户端随以后的每个请求发送该 token

  • 问题是,我们应该如何实现注销?
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import java.util.Date;
    
    class TokenAuthenticationService {
        static final long EXPIRATIONTIME = 864_000_000; // 10 days
        static final String SECRET = "ThisIsASecret";
        static final String TOKEN_PREFIX = "Bearer";
        static final String HEADER_STRING = "Authorization";
    
        static void addAuthentication(HttpServletResponse res, String username) {
            String JWT = Jwts
                    .builder()
                    .setSubject(username)
                    .setExpiration(
                            new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                    .signWith(SignatureAlgorithm.HS512, SECRET).compact();
            res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
        }
    
        static Authentication getAuthentication(HttpServletRequest request, UserDetailsService customUserDetailsService) {
            String token = request.getHeader(HEADER_STRING);
            if (token != null) {
                // parse the token.
                Claims claims = Jwts.parser().setSigningKey(SECRET)
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
                String userName = claims.getSubject();
                Date expirationTime = claims.getExpiration();
                if (expirationTime.compareTo(new Date()) < 0) {
                    return null;
                }
                UserDetails user = customUserDetailsService.loadUserByUsername(userName);
                return user != null ? new UsernamePasswordAuthenticationToken(user.getUsername(),
                        user.getPassword(), user.getAuthorities()) : null;
            }
            return null;
        }
    }
    
    addAuthentication类使用JWTLoginFilter在登录时发送身份验证码,即“getAuthentication is used by the JWTAuthenticationFilter”,该过滤器将所有请求过滤到端点。

    最佳做法是什么?

    最佳答案

    我认为这里没有最佳实践。我想这取决于您正在构建的应用程序及其要求。

    JWT的好处是它们是无状态的。您无需查询数据库即可验证 token 。当您希望减少数据库的负载时,这很好,但是当您想要使现有的未过期 token 无效时,这是不好的。

    可能的解决方案:

  • 将JWT存储在数据库中。您可以检查哪些 token 是有效的,哪些 token 已被撤销,但是在我看来,这完全违背了使用JWT的目的。
  • 从客户端删除 token 。这将阻止客户端发出经过身份验证的请求,但是如果 token 仍然有效并且其他人可以访问它,则仍可以使用 token 。这引出我的下一个观点。
  • token 生存期短。让 token 快速过期。根据应用程序的不同,可能需要几分钟或半个小时。当客户端删除其 token 时,在很短的时间内仍可以使用它。从客户端删除 token 并缩短 token 生命周期将不需要在后端进行重大修改。但是短暂的 token 生存期将意味着由于 token 已过期,用户将不断被注销。
  • 旋转 token 。也许引入刷新 token 的概念。用户登录时,向他们提供JWT和刷新 token 。将刷新 token 存储在数据库中。对于经过身份验证的请求,客户端可以使用JWT,但是当 token 过期(或即将过期)时,让客户端使用刷新 token 发出请求,以换取新的JWT。这样,您只需要在用户登录或要求新的JWT时访问数据库。当用户注销时,您将需要使存储的刷新 token 无效。否则,即使用户已注销,在连接上监听的人仍然可以获取新的JWT。
  • 创建JWT黑名单。根据到期时间,当客户端删除其 token 时,它可能在一段时间内仍然有效。如果 token 生命周期很短,则可能不是问题,但是如果您仍然希望 token 立即失效,则可以创建 token 黑名单。当后端收到注销请求时,请从请求中获取JWT并将其存储在内存数据库中。对于每个经过身份验证的请求,您都需要检查内存数据库以查看 token 是否已失效。为了缩小搜索空间,您可以从黑名单中删除已经过期的 token 。
  • 10-07 19:29
    查看更多