JWT 实战:在 Spring Boot 中的使用
文章目录
- 一、JWT简介
- 二、JWT 的结构
- 三、JWT 的生成过程
- 四、JWT 验证过程
- 五、JWT 的应用场景
- 六、JWT的实现
- 6.1 登录接口
- 6.2 校验 Token 接口
- 6.3 `jwtUtil` 类
- 七、总结
一、JWT简介
JWT(JSON Web Token)是一种用于客户端和服务器之间安全传输信息的开放标准(RFC 7519)。JWT 的目的是通过一种 compact、URL-safe 的方式传递信息,以便在 web 应用中广泛使用,尤其是在用户认证和授权中。
二、JWT 的结构
JWT 由三部分组成,每部分之间用 .
进行分隔,格式为: header.payload.signature
- Header(头部):描述 JWT 的元数据,通常包含令牌类型(
JWT
)和签名算法(例如:HS256
、RS256
),头部通常使用 Base64 编码后成为 JWT 的第一部分。 - Payload(负载):包含用户信息或其他声明(Claims)。这里的内容是 JSON 格式的声明,可以是公开的(如
name
,exp
)或私有的。 - Signature(签名):用来验证消息的完整性,防止数据被篡改。它是通过 Header 和 Payload 以及密钥(secret)一起生成的。
三、JWT 的生成过程
JWT 的生成通常会通过以下步骤:
-
Header:指定签名算法(通常是 HMAC SHA256 或 RSA)。
{ "alg": "HS256", "typ": "JWT" }
-
Payload:包含具体的声明信息,如:
sub
(Subject):标识该 JWT 的主体(通常是用户的 ID)。exp
(Expiration):标识 JWT 的过期时间。iat
(Issued At):标识 JWT 的签发时间。iss
(Issuer):标识 JWT 的签发者。aud
(Audience):指定此 JWT 的接收者。
示例 Payload:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
-
Signature:使用 Header 和 Payload 以及签名密钥生成签名。
- 如果使用 HMAC SHA256 算法,则签名生成过程为:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
- 如果使用 HMAC SHA256 算法,则签名生成过程为:
-
最终生成的 JWT:将 Header、Payload 和 Signature 连接起来,以
.
分隔。header.payload.signature
四、JWT 验证过程
JWT 的验证主要是检查签名是否有效以及 exp
(过期时间)等是否符合要求。
- 从请求头中提取 JWT。
- 使用与生成 JWT 时相同的算法、密钥来验证签名是否有效。
- 如果签名有效,检查 JWT 的有效期(
exp
),以及是否存在其他不符合要求的声明。 - 如果有效,服务器允许访问该资源;如果无效,返回错误信息。
五、JWT 的应用场景
-
认证(Authentication):JWT 最常见的应用场景是身份认证。例如,用户登录时,服务器验证其用户名和密码后生成一个 JWT,并将其返回给客户端。客户端在之后的每次请求中将该 JWT 放入 HTTP 请求头(Authorization)。
-
授权(Authorization):JWT 也常用于访问控制。例如,通过 JWT 可以携带用户的权限信息,确保用户有权限访问某些资源。
-
信息交换(Information Exchange):JWT 可以安全地传递信息,因为它是自包含的,信息在签发时已经被加密验证,因此不容易篡改。
六、JWT的实现
6.1 登录接口
/login
接口用于用户登录并生成 JWT:
@PostMapping("/login")
public Result login(@RequestBody LoginForm loginForm, HttpSession session){
// 验证验证码
String code = (String) this.redisTemplate.opsForValue().get(loginForm.getUuid());
if(code == null || !code.equals(loginForm.getCaptcha())) {
return Result.error("验证码错误");
}
// 验证用户名和密码
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", loginForm.getUsername());
User user = this.userService.getOne(queryWrapper);
if(user == null || !SecureUtil.sha256(loginForm.getPassword()).equals(user.getPassword())) {
return Result.error("用户名或密码错误");
}
// 检查用户是否被锁定
if(user.getStatus() == 0) {
return Result.error("账号已被锁定,请联系管理员");
}
// 登录成功,生成 Token
session.setAttribute("user", user);
String token = this.jwtUtil.createToken(String.valueOf(user.getUserId()));
this.redisTemplate.opsForValue().set("communityuser-" + user.getUserId(), token, jwtUtil.getExpire());
Map<String, Object> map = new HashMap<>();
map.put("token", token);
map.put("expire", jwtUtil.getExpire());
// 返回生成的 token
return Result.ok().put("data", map);
}
如果登录成功,生成一个 JWT Token,并存储到 Redis 中。返回给客户端该
token
和过期时间。
6.2 校验 Token 接口
/checkToken
接口用于验证客户端传来的 token 是否有效:
@GetMapping("/checkToken")
public Result checkToken(HttpServletRequest request){
String token = request.getHeader("token");
boolean result = this.jwtUtil.checkToken(token);
if(result) return Result.ok().put("status", "ok");
return Result.ok().put("status", "error");
}
- 提取 Token:从请求头中提取
token
。- 验证 Token:调用
jwtUtil.checkToken
方法来验证token
是否有效。- 返回验证结果:如果
token
有效,返回"status": "ok"
;如果无效,返回"status": "error"
。
6.3 jwtUtil
类
jwtUtil
类封装了生成和验证 JWT 的操作,通常包括以下方法:
-
createToken
:生成一个新的 JWT,包含用户信息和过期时间。public String createToken(String userId) { return Jwts.builder() .setSubject(userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 设置过期时间 .signWith(SignatureAlgorithm.HS256, secretKey) // 使用密钥进行签名 .compact(); }
-
checkToken
:验证传入的token
是否有效。public boolean checkToken(String token) { try { Jws<Claims> claimsJws = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token); // 解析并验证签名 return true; } catch (Exception e) { return false; // 验证失败 } }
七、总结
- JWT 用于用户认证和授权,包含三部分:头部、负载和签名。
- 登录流程:通过验证用户名、密码和验证码,生成 JWT Token,并通过 Redis 存储用户的
token
。 - Token 校验:通过
jwtUtil.checkToken
方法验证客户端提供的token
是否有效。 - 安全性:JWT 通过密钥加密生成和验证签名,确保信息传输的安全性。
JWT 的优势在于无状态认证、跨域认证、以及扩展性强,但也需要注意密钥管理、过期时间控制等安全细节。