1. JWT,全称是Json Web Token, 是一种JSON风格的轻量级的授权和身份认证规范,可实现无状态、分布式
的Web应用授权!
2. JWT 由三部分组成:
- 头部(Header): 通常包含令牌的类型(即 JWT)和加密算法(如 HMAC SHA256 或 RSA)。例如:
{
"alg": "HS256",
"typ": "JWT"
}
- 载荷(Payload): 包含要传递的声明(Claims)。声明总共可以包括如下七项,但是不是必须全部包含,可以根据业务具体需要进行设置。如下。
{
iss: jwt签发者
sub: jwt所面向,使用jwt的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须大于签发时间
nbf: 定义在指定时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
}
3. 签名(Signature): 通过对头部和载荷进行编码,然后用指定的算法和密钥生成的签名,用于验证数据的完整性和真实性,其中secret是密钥,不可泄漏,并且其生成也用到了头部信息和载荷信息。例如:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
JWT 的这三部分通过点(.
)连接在一起形成最终的 JWT。例如:eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogdHJ1ZX0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
4.校验流程:
-
生成 JWT: 服务器在用户登录时生成 JWT。生成时,服务器将用户的身份信息(例如用户 ID 和角色)放入载荷中,并用密钥对其进行签名,生成完整的 JWT。
-
传递 JWT: JWT 可以通过 HTTP 请求的头部、查询参数或 cookies 进行传递。常见的做法是将 JWT 放在
Authorization
头部中,如Authorization: Bearer <token>
。 -
验证 JWT: 服务器在收到请求时,解码 JWT 头部和载荷,使用密钥验证签名是否正确。如果签名验证成功且载荷中的信息有效(例如,未过期),则接受请求;否则拒绝请求。
5.代码示例,工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Component
public class JwtUtils {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
/**
* 生成token
* @param username
* @return
*/
public String generateToken(String username) {
String token = Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
// 将 Token 存储到 Redis 中
redisTemplate.opsForValue().set(username, token, expiration, TimeUnit.MILLISECONDS);
return token;
}
/**
* 验证token
* @param token
* @return
*/
public boolean validateToken(String token) {
// 从 Token 中获取用户名
String username = getUsernameFromToken(token);
// 从 Redis 中获取存储的 Token
String storedToken = redisTemplate.opsForValue().get(username);
// 判断 Redis 中存储的 Token 是否与传入的 Token 相同
return storedToken != null && storedToken.equals(token);
}
/**
* 删除token
* @param username
*/
public void removeToken(String username) {
// 从 Redis 中删除 Token
redisTemplate.delete(username);
}
/**
* 根据token获取用户信息
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims.getSubject();
}
/**
* 判断token是否存在
* @param username
* @return
*/
public String getTokenIfExists(String username) {
// Check if a valid token exists in Redis for the given username
String storedToken = redisTemplate.opsForValue().get(username);
// Validate the stored token
if (storedToken != null && validateToken(storedToken)) {
return storedToken;
} else {
return null;
}
}
}
6. 优缺点。jwt是无状态,自包含式(自身包含了加密算法以及加密数据)的权限校验框架。可以不同于session,使得可以不依赖于服务端的数据,减轻了服务的的负担,并且前端可以不用考虑请求被分发到哪一台服务器。
缺点在于。生成的token很长,且都所有请求都比带上,增加网络传输开销;过期时间不可控,一旦生成了之后,比如用户注销登陆,token仍然是有效的,除非另外开发这部分过期功能。令牌中包含了用户信息,如果令牌被盗取,攻击者可以获得用户的敏感信息,因此需要对令牌进行严格的存储和管理。