JWT 构成
JWT 是一个很长的字符串,看起来如下所示,中间用“.”隔开分为三部分。
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJjdXN0b20gc3ViamVjdCIsImlzcyI6InpoYW5nc2FuIiwiYXVkIjoibm9ybWFsIHVzZXIiLCJuYmYiOjE2NjYxNzAwMjEsImlhdCI6MTY2NjE2OTk5MSwiZXhwIjoxNjY2MTcwMDUxfQ.QgGH1zo4FXOmG4mIrZDRQ-KaROgLEuKjlL78bGSRg0U
其中分为三部分为“Header”、“Payload”、“Signature”。
Header
头(Header):eyJhbGciOiJIUzI1NiJ9,是一个 json 对象,描述 JWT 的元数据,包含所使用的签名算法等等。使用 Base64 算法将数据进行加密。可使用 Base64 解密工具解密,解密之后的数据如下。
{"alg":"HS256"}
Payload
负载(Payload):也是一个 JSON 对象,使用 Base64 算法将数据进行加密。
eyJzdWIiOiJjdXN0b20gc3ViamVjdCIsImlzcyI6InpoYW5nc2FuIiwiYXVkIjoibm9ybWFsIHVzZXIiLCJuYmYiOjE2NjYxNzAwMjEsImlhdCI6MTY2NjE2OTk5MSwiZXhwIjoxNjY2MTcwMDUxfQ
解密之后的数据
{"sub":"custom subject","iss":"zhangsan","aud":"normal user","nbf":1666170021,"iat":1666169991,"exp":1666170051}
解密后的 JSON 字串中,有如下官方的字段,且均为可选
- exp(expire):过期时间
- sub(subject):主题
- iss(issuer):签发人
- aud(audience):受众
- nbf(not before):生效时间
- iat(issued at):签发时间
- jti(JWT ID):编号
基于 Java 实现的 JJWT,过期时间和生效时间可以设定,在特定的情况下会抛出 ExpiredJwtException
和 PrematureJwtException
异常。
Signature
签名(Signature):8P1Gnfp1BYL_ViewC_yk1vwsjqF__-UagQDvPJADV9o,签名可防止数据被篡改。
签名算法如下
method(base64(header) + "." + base64(payload) + "." + secret)
其中 method 代表的算法名称,可用的算法有很多,在 JJWT 中可使用的签名算法都放在 SignatureAlgorithm 类中,默认使用的签名算法为 HMAC-SHA256。
secret:密钥必须在服务器中保管好,不能随意发给用户。我们使用 JJWT 的 secret 生成器来生成密钥,当然自己来生成也是可以的。使用生成器生成的密钥如下。
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);
特点与用途
主要用于身份认证和信息交换。
优点:不需经过数据库校验
缺点:
- 一旦签发,只能等到过期才会失效,无法在期间手动终止 token。
- JWT 是明文的,有敏感数据时必须要将 JWT 加密,传输时尽量使用 HTTPS 协议传输数据。
实践一下
一个简单的控制台程序,引入 Java JWT 的依赖
<!-- jjwt核心 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<!-- 包含签名算法 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<!-- 处理jwt中的json部分 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
示例程序
public class MainApp {
// 生成的密钥
static SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256);
static String jwt = "";
// 加密方法
public static void enc() {
LocalDateTime now = LocalDateTime.now().plusSeconds(30);
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
Date nb = Date.from(instant); // 生效时间
now = now.plusSeconds(30);
instant = now.atZone(ZoneId.systemDefault()).toInstant();
Date expire = Date.from(instant); // 失效时间
String compact = Jwts.builder().serializeToJsonWith(new JacksonSerializer<>())
.setHeader(new HashMap<String, Object>() {{
}})
.setSubject("Joe") // 主题
.setIssuer("zhangsan") // 设置签发人
.setSubject("custom subject") // 自定义主题
.setAudience("normal user") // 设置受众
.setNotBefore(nb) // 设置生效时间
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(expire) // 设置过期时间
.signWith(key, SignatureAlgorithm.HS256) // 生成签名
.compact();
System.out.println(compact);
jwt = compact;
}
// 解密方法
public static void dec() {
JwtParser build = Jwts.parserBuilder()
.setSigningKey(key)
.deserializeJsonWith(new JacksonDeserializer<>()).build();
try {
// 获取 payload 信息
Jws<Claims> claimsJws = build.parseClaimsJws(jwt);
Claims body = claimsJws.getBody();
System.out.println("主题:" + body.getSubject());
System.out.println("受众:" + body.getIssuer());
// 获取生效时间
Date date = body.getNotBefore();
String format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("生效时间:" + format);
// 获取签发时间
date = body.getIssuedAt();
format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("签发时间:" + format);
// 获取过期时间
date = body.getExpiration();
format = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("过期时间:" + format);
System.out.println(body.getSubject());
} catch (ExpiredJwtException e) {
System.out.println("token到期了,重新签发一下!");
} catch (PrematureJwtException e) {
System.out.println("token还未生效,请再等等!");
}
}
// 主程序
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
String s = scanner.nextLine();
if(s.equals("exit")) {
break;
}
if(s.equals("enc")) {
enc();
}
if(s.equals("dec")) {
dec();
}
}
}
}
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。