一、JWT 是什么?
JWT(JSON Web Token) 是一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全传输信息。它由三部分组成,用点号 .
分隔:
Header(头部):声明令牌类型(如 JWT
)和签名算法(如 HS256
/RS256
)
示例:
{ "alg": "HS256", "typ": "JWT" }
Base64Url编码后:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload(负载):包含声明信息(Claims),如用户身份(sub
)、过期时间(exp
)、权限等。数据以 Base64 编码,非加密,敏感信息需额外加密
示例:
{ "sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022 }
Base64Url编码后:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
Signature(签名):对 Header 和 Payload 用密钥签名,确保数据完整性和防篡改
示例(使用密钥
your-256-bit-secret
):HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret )
签名结果(Base64Url编码后):
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
完整JWT示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
完整jJWT = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + 对前面那两个base64编码的字符串 用"."组合在一起 再用密钥加密一次
如果需要生成或调试JWT,可以使用在线工具: jwt.io
二、怎么用?工作流程
用户登录:客户端发送凭据(如用户名/密码)到服务器
生成 JWT:服务器验证凭据后,生成 JWT 并返回给客户端
存储 JWT:客户端将 JWT 存储在
localStorage
、sessionStorage
或 Cookie 中发送 JWT:客户端在后续请求的
Authorization
头中携带 JWT(如Bearer <token>
)验证 JWT:服务器验证签名、有效期及声明,确认请求合法
三、什么时候用它?
适用场景
用户认证与授权:
登录后生成 JWT 作为身份凭证,用于后续 API 请求的权限验证。
单点登录(SSO):
用户登录一次,JWT 在多个关联系统间共享身份状态。
API 安全:
微服务间通过 JWT 传递用户身份和权限,替代 Session 会话。
无状态系统:
需横向扩展的分布式架构(如云原生应用)。
临时访问:
密码重置链接、短期资源访问令牌。
四、用户登录了,如果中途过期了怎么办?
当用户登录后,如果 JWT 过期,可以通过 双 Token 机制(Access Token + Refresh Token),生成双Token
// 生成双 Token
String accessToken = JwtUtil.generateAccessToken(request.getUsername()); // 推荐设置15-30 分钟
String refreshToken = JwtUtil.generateRefreshToken(request.getUsername()); // 推荐设置7天
创建拦截器,在请求前检查 Access Token 是否过期,若过期则用 Refresh Token 获取新 Access Token,来替换原来的Access Token,从而实现无感知刷新。 如果Refresh Token 过期了呢?当然是跳转登录界面重新登录了。
五、用户主动登出时,如何立即失效让Token失效呢?
当用户主动登出时,需要确保所有相关的令牌(Token)立即失效,即使它们尚未过期。
因为JWT的特性是无状态的,服务端无法直接修改或删除已签发的 Token。前端直接清理所有token,后端可以折中的在Redis引入一个黑名单,使它失效,虽然使用 Redis 存储 Token 黑名单确实会破坏 JWT 的“无状态”设计原则,但这是一种必要的权衡。
1. 立即失效 Access Token
解决方案:
短有效期:Access Token 本身已设为短期(如 30 分钟),登出后等待其自然过期。
客户端删除:前端清除存储的 Access Token(
localStorage
或内存)。
2. 强制失效 Refresh Token
关键步骤(必须服务端参与):
服务端维护 Refresh Token 黑名单。
用户登出时,将 Refresh Token 标记为失效(如存入数据库或 Redis)。
后续刷新请求时,检查 Refresh Token 是否已被撤销。
3. 清除客户端存储
前端删除所有 Token 和会话相关的存储:
// 前端登出逻辑
function logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
// 如果是 Cookie,设置过期时间为过去
document.cookie = 'refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
// 跳转登录页
window.location.href = '/login';
}
服务端实现方案
使用Refresh Token 黑名单(Redis + JWT)
@PostMapping("/logout")
public ResponseEntity<?> logout(
@RequestHeader("Authorization") String accessToken,
@CookieValue(value = "refresh_token", required = false) String refreshToken
) {
// 1. 解析 Access Token 获取用户名(无需验证过期)
String username = JwtUtil.getUsernameFromToken(accessToken.replace("Bearer ", ""));
// 2. 将 Refresh Token 加入黑名单(存储哈希值)
String refreshTokenHash = hash(refreshToken);
redisTemplate.opsForValue().set(
"blacklist:" + refreshTokenHash,
username,
JwtUtil.getRemainingExpiration(refreshToken) // 剩余有效期
);
// 3. 清除客户端 Cookie(可选)
ResponseCookie cookie = ResponseCookie.from("refresh_token", "")
.maxAge(0)
.path("/")
.httpOnly(true)
.secure(true)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body("登出成功");
}
验证 Refresh Token 时检查黑名单:
public boolean isRefreshTokenValid(String refreshToken) {
// 检查签名和过期时间
if (!JwtUtil.validateToken(refreshToken)) return false;
// 检查是否在黑名单中
String refreshTokenHash = hash(refreshToken);
return !redisTemplate.hasKey("blacklist:" + refreshTokenHash);
}
总结
JWT 是一种用签名保护的 JSON 数据令牌,适合无状态认证,但需注意安全设计(密钥、过期时间、敏感数据)。