是什么?
身份令牌是现代Web应用和移动应用中用于管理用户会话的核心机制
为什么需要令牌
没有令牌的世界——Cookie/Session(基于服务器存储的会话) 的传统模式
- 你输入用户名密码登录。
- 服务器验证通过,在服务器的内存里创建一个Session(会话记录),并生成一个对应的 Session ID(会话ID)。
- 服务器把 Session ID 发给浏览器,浏览器存在 Cookie 里。
- 以后每次请求,浏览器带上这个 ID。
- 关键点:服务器收到 ID 后,必须去自己的内存(或数据库)里查找这个 Session 是否存在、是否过期
令牌的出现
无状态(Stateless)。服务器不再存储 Session,而是把用户信息(如用户ID、昵称、过期时间)经过加密,直接生成一个令牌交给客户端。 客户端每次带着这个令牌来,服务器只需要验证令牌本身的签名就能确认身份,无需查数据库或内存
令牌三步走
常见令牌
JWT(JSON Web Token)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 由三部分组成:Header(头部)、Payload(负载)和Signature(签名)
- 头部(Header):声明了加密算法(比如用了什么哈希算法)。
- 载荷(Payload):存放实际数据的地方。比如用户ID(user_id: 123)、用户名、过期时间(exp)。注意:这里的数据只是用Base64编码(可解码看到原文),所以千万不要在JWT里存放密码等敏感信息。
- 签名(Signature):这是最关键的防伪部分。服务器使用只有自己知道的密钥,对前两部分进行加密生成签名。如果有人篡改了令牌里的用户ID,签名就无法匹配,服务器会直接拒绝。
jwt demo
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- 或 jjwt-gson,用于 JSON 处理 -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
package com.jysemel;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtExample {
// 生成一个足够安全的签名密钥(实际应用中应从配置文件读取或使用密钥库)
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); // HS256 需要 256位密钥
// 过期时间:1小时(毫秒)
private static final long EXPIRATION_TIME = 3600000;
public static void main(String[] args) {
// 1. 生成 JWT
String token = generateToken("user123", "admin");
System.out.println("生成的 JWT:\n" + token);
// 2. 验证并解析 JWT
parseToken(token);
}
/**
* 生成 JWT
* @param subject 用户标识(如用户名、用户ID)
* @param role 用户角色(自定义声明示例)
* @return JWT 字符串
*/
public static String generateToken(String subject, String role) {
// 设置头部(可选,默认即可)
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
// 设置载荷(Claims)
JwtBuilder builder = Jwts.builder()
.setHeader(header) // 头部
.setSubject(subject) // 主题(通常是用户标识)
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
.setIssuer("MyApp") // 签发者
.setAudience("MyClient") // 接收方
.setId(java.util.UUID.randomUUID().toString()) // JWT ID,用于防止重放攻击
.claim("role", role) // 自定义声明:角色
.claim("dept", "engineering") // 自定义声明:部门
.signWith(SECRET_KEY); // 签名
return builder.compact();
}
/**
* 解析 JWT,验证签名并提取数据
* @param token JWT 字符串
*/
public static void parseToken(String token) {
try {
// 创建解析器,设置签名密钥
JwtParser parser = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build();
// 解析 JWT,得到 Jws<Claims> 对象
Jws<Claims> jws = parser.parseClaimsJws(token);
// 获取头部
JwsHeader header = jws.getHeader();
System.out.println("\n--- 解析结果 ---");
System.out.println("算法:" + header.getAlgorithm());
System.out.println("类型:" + header.getType());
// 获取载荷
Claims claims = jws.getBody();
System.out.println("主题(subject):" + claims.getSubject());
System.out.println("签发时间:" + claims.getIssuedAt());
System.out.println("过期时间:" + claims.getExpiration());
System.out.println("签发者:" + claims.getIssuer());
System.out.println("接收方:" + claims.getAudience());
System.out.println("JWT ID:" + claims.getId());
System.out.println("角色(自定义声明):" + claims.get("role"));
System.out.println("部门(自定义声明):" + claims.get("dept"));
} catch (ExpiredJwtException e) {
System.err.println("JWT 已过期:" + e.getMessage());
} catch (UnsupportedJwtException e) {
System.err.println("不支持的 JWT:" + e.getMessage());
} catch (MalformedJwtException e) {
System.err.println("JWT 格式错误:" + e.getMessage());
} catch (SignatureException e) {
System.err.println("JWT 签名验证失败:" + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("JWT 参数非法:" + e.getMessage());
}
}
}
OAuth2.0(不透明令牌)
OAuth2.0 是一个开放授权标准,它允许用户授权第三方应用访问其资源,而无需将用户名和密码提供给第三方应用。
- OAuth 2.0 授权码模式流程图(时序图)
- OAuth 2.0 核心角色关系图