1. 认识JWT
1.1 什么是JWT
Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递 json 对象,传递的信息经过数字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA 的公钥/私钥对来签名,防止被篡改。
优点:
1. jwt 基于 json,非常方便解析。
2. 可以在令牌中自定义丰富的内容,易扩展。
3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4. 资源服务使用JWT可不依赖认证服务即可完成授权。
缺点:
1. JWT令牌较长,占存储空间比较大。
1.2 JWT构成
1.2.1 头部
JWT 由三部分构成:头部、负载、签证。JWT 的头部承载两部分信息,声明类型以及加密算法。
- 加密解密网站:https://tool.chinaz.com/tools/base64.aspx ```java { ‘typ’: ‘JWT’, ‘alg’: ‘HS256’ }
// 头部使用Base64加密 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
<a name="J3Q0Y"></a>
### 1.2.2 负载
负载,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
- 标准中注册的声明(建议但不强制使用)
- **iss**: jwt 签发者
- **sub**: jwt 所面向的用户
- **aud**: 接收 jwt 的一方
- **exp**: jwt 的过期时间,这个过期时间必须要大于签发时间
- **nbf**: 定义在什么时间之前,该 jwt 都是不可用的.
- **iat**: jwt 的签发时间
- **jti**: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。
- 公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
- 私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。
```java
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
// 加密
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
1.2.3 签证
jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要 base64 加密后的 header 和 base64加密后的 payload 使用.连接组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了jwt的第三部分。
注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了。
1.3 JWT应用
一般是在请求头里加入 Authorization,并加上Bearer 标注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服务端会验证 token,如果验证通过就会返回相应的资源。整个流程就是这样的:
2. 认识JJWT
2.1 什么是JJWT
JJWT 是一个提供端到端的 JWT 创建和验证的 Java 库。永远免费和开源(Apache License,版本2.0),JJWT 很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。规范官网:https://jwt.io/
2.2 引入依赖
<!-- jwt 依赖配置-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.3 快速入门
1、引入依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springsecurity-demo</artifactId>
<groupId>com.xuwei</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springboot-jjwt</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- jwt 依赖配置-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
2、自定义 JWT 测试类。
@SpringBootTest
public class JjwtTest {
/**
* 创建Token
*/
@Test
public void testCreateToken() {
System.out.println("获取token");
// 创建一个JwtBuilder对象
JwtBuilder jwtBuilder = Jwts.builder()
// 声明的标识{"jti":"888"}
.setId("888")
// 主体,用户{"sub":"Rose"}
.setSubject("Rose")
// 创建日期{"ita":"xxxxxx"}
.setIssuedAt(new Date())
// 签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, "xxxx");
// 获取jwt的token
String token = jwtBuilder.compact();
System.out.println(token);
// 三部分的base64解密
String[] tokenSplit = token.split("\\.");
// {"alg":"HS256"}
System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[0]));
// {"jti":"888","sub":"Rose","iat":1646360468
System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[1]));
// 无法解密
System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[2]));
}
/**
* token过期校验
*/
@Test
public void testCreateTokenHasExp() {
long now = System.currentTimeMillis();
// 设置过期时间,这里是1分钟后的时间长整型
long exp = now + 60 * 1000;
JwtBuilder jwtBuilder = Jwts.builder()
//声明的标识{"jti":"888"}
.setId("888")
//主体,用户{"sub":"Rose"}
.setSubject("Rose")
//创建日期{"ita":"xxxxxx"}
.setIssuedAt(new Date())
//签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, "xxxx")
// 设置过期时间
.setExpiration(new Date(exp));
String token = jwtBuilder.compact();
System.out.println(token);
}
/**
* 自定义Claims,在负载里存储更多的信息可以自定义
*/
@Test
public void testCreateTokenByClaims() {
long now = System.currentTimeMillis();
// 设置过期时间,这里是1分钟后的时间长整型
long exp = now + 60 * 1000;
JwtBuilder jwtBuilder = Jwts.builder()
//声明的标识{"jti":"888"}
.setId("888")
//主体,用户{"sub":"Rose"}
.setSubject("Rose")
//创建日期{"ita":"xxxxxx"}
.setIssuedAt(new Date())
//签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, "xxxx")
// 设置过期时间
.setExpiration(new Date(exp))
// 直接传入map也可以 .addClaims(map)
.claim("roles", "admin")
.claim("logo", "shsd.jpg");
String token = jwtBuilder.compact();
System.out.println(token);
}
/**
* 解析token
*/
@Test
public void testParseToken() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQ2MzYxMjIwLCJleHAiOjE2NDYzNjEyODAsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoic2hzZC5qcGcifQ.41yPcSw0Lp_AQ0b3nXGh513IUP8qs7pc_UJgym4olZ8";
// 解析token获取负载中的声明对象
Claims claims = Jwts.parser()
.setSigningKey("xxxx")
.parseClaimsJws(token)
.getBody();
// 打印声明的属性
System.out.println("id:"+claims.getId()); // 888
System.out.println("subject:"+claims.getSubject()); // Rose
System.out.println("issuedAt:"+claims.getIssuedAt()); // Fri Mar 04 10:22:04 CST 2022
DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("签发时间:"+sf.format(claims.getIssuedAt()));
System.out.println("过期时间:"+sf.format(claims.getExpiration()));
System.out.println("当前时间:"+sf.format(new Date()));
System.out.println("roles:"+claims.get("roles"));
System.out.println("logo:"+claims.get("logo"));
}
}