JWT身份验证:如何实现注销?

2022-09-05 00:15:01

我为我的Spring启动应用程序实现了JWT身份验证。总的来说,它的工作原理如下:

  1. 客户端将用户名、密码发送到登录端点。
  2. 服务器检查提供的凭据是否有效。
  3. 如果不是,它将返回错误
  4. 如果是,它将返回一个令牌,该令牌实际上包括
  5. 客户端在将来的每个请求中都会发送该令牌

问题是,我们应该如何实现注销?

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由该类在登录时用于发送身份验证代码'getAuthenticationJWTAuthenticationFilter',用于筛选对端点的所有请求。JWTLoginFilteris used by the

这里的最佳实践是什么?


答案 1

我不认为这里有最佳实践。我想这取决于你正在构建的应用程序和它的要求。

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

可能的解决方案:

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

答案 2

我不知道什么是最佳实践,但是在我见过的内部系统中,有一个中央身份验证管理器知道所有当前有效的身份验证令牌,因此注销只需从有效令牌集合中删除令牌即可。

因此,下次询问身份验证管理器令牌是否有效时,它将以“否”进行响应。


推荐