写于2019年年底,2020年春,新年好!
JWT
全称 Json Web Token
用于用户认证
用于前后端分离项目(App/微信小程序 无法产生cookie的项目)
文中所提到的 Token泛指身份验证时使用的令牌,而JWT,是json 格式的 web token,两者稍作区别
JWT的构成
JWT 官网 点击前往 ,下列数据解释官网内容:
由三段字符串组成,两端中间用
.
分隔
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- 第一段字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
HEADER:
ALGORITHM
&TOKEN TYPE
包含生成token使用的算法与token类型
{
"alg": "HS256", //ALGORITHM ,默认算法 哈希256
"typ": "JWT" //TOKEN TYPE ,token类型
}
将该JSON字符串做 base64Url 编码 得到
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- 第二段字符串:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
PAYLOAD:
DATA
数据载体,可以有自定义数据
{
"sub": "1234567890", // 自定义数据
"name": "John Doe", // 自定义数据
"iat": 1516239022 // token起作用时间 、生产日期
}
将该JSON字符串做 base64Url 编码得到
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
base64Url 可被解码,所以不宜将敏感信息写在token中
- 第三段字符串:
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
VERIFY SIGNATURE
签名验证
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
) secret base64 encoded
- 将第一段 + 第二段 字符串拼接起来(中间用
.
)- 将拼接完成的字符串进行加密, 算法 + 盐 + 密钥
- 对算法 加密后的密文再做base64Url编码
JWT实现认证的大致过程
假设使用HS256算法
-
用户提交用户名+密码发送请求给服务端,服务端接受参数使用JWT 创建token返回 (先登录)
-
用户第二次发送请求,带上token (登录后的操作 )
-
服务端接受token ,将token 分割开 (切割成三部分)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-
对第二段字符串进行 base64Url 解密并获取
PAYLOAD
数据{ "sub": "1234567890", // 自定义数据 "name": "John Doe", // 自定义数据 "iat": 1516239022 // token起作用时间 、生产日期 }
-
检测
PAYLOAD
中的信息是否过期(比较iat
和exp
时间)为了保证前面两段数据没有被恶意篡改,来校验第三段字符串:
-
因为 HS256 不能被反解密(RS256、MD5 亦是如此),所以将第一、二段字符串拼接, 进行 HS256
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` + eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ //toHS256
-
拿着生成的 HS256 密文与第三段字符串进行比较
- 一致,则校验通过
- 不同,则不通过
JWT 在JAVA中的应用
Maven库搜索 JWT, jjwt使用率排行第一(优点的话,我粗糙的看了下实现的代码,简明易懂,易使用,但网上说说封装的时候获取信息的能力有限),我这里使用的是 auth0,加入pom.xml 依赖
对于auth0的缺点,应该就是在验证RSA256 加密后的 token的时候,需要给算法实例传递两个key(公钥 + 私钥),这明显不太符合常规情况(常规情况是只需提供公钥即可)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.9.0</version>
</dependency>
观察GitHub中的教程:
https://github.com/auth0/java-jwt
JWT 规定了7个官方字段,提供使用。
- iss (issuer):发布者
- sub (subject):主题
- iat (Issued At):生成签名的时间
- exp (expiration time):签名过期时间
- aud (audience):观众,相当于接受者
- nbf (Not Before):生效时间
- jti (JWT ID):编号
Using HS256
HS256,签名/验证的时候用的都是同一个密钥,称为对称算法
创建JWT
首先拿到算法实例,用于创建后的签入 sign
(传入算法实例)
Algorithm algorithm = Algorithm.HMAC256("secret"); //secret 密钥,只有服务器知道
观察源码:
public static Algorithm HMAC256(String secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS256", "HmacSHA256", secret);
}
实际使用的时候,将 secret 字符串弄得长点,复杂点
使用JWT.create()
创建一个 JWTCreator
实例
String token = JWT.create()
使用sign()
签入algorithm
在签入之前:
使用withIssuer()
给PAYLOAD添加一跳数据 => token发布者
使用withClaim()
给PAYLOAD添加一跳数据 => 自定义声明 (key,value)
使用withIssuedAt()
给PAYLOAD添加一条数据 => 生成时间
使用withExpiresAt()
给PAYLOAD添加一条数据 => 保质期
@Test
public void creatToken(){
try{
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0") // 发布者
.withIssuedAt(new Date()) // 生成签名的时间
.withExpiresAt(DateUtils.addHours(new Date(),2)) // 生成签名的有效期,小时
.withClaim("name","wuyuwei") // 插入数据
.sign(algorithm);
System.out.println(token);
}catch(JWTCreationException e){
e.printStackTrace();
//如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出JWTCreationException异常。
}
}
withIssuer()
用于对参数 添加声明,观察源码:public JWTCreator.Builder withIssuer(String issuer) { this.addClaim("iss", issuer); return this; } private