JWT

这是什么东西?

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;它是分布式服务权限控制的标准解决方案!主要用来解决跨域认证问题

它跟RBAC的区别:两者不冲突,在项目中后台权限服务的数据库设计使用RBAC,而前端项目访问后台微服务的权限校验使用jwt

为什么要用这个东西?

对于传统的网站来说,需要使用session配合cookie储存用户的验证信息,但是一个大公司中可能会有多个服务项目,这就要求我们的服务产生的用户数据共享(session共享)

举个例子,a网站与b网站都是一个公司的项目,那现在要求我们在一个网站登陆了就可以在另一个网站同时登陆,如何实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

到此,我们从更高的层面了解了JWT是怎样完成无状态的第三方认证,接下来让我们了解它的实现细节。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回用户

1
2
3
4
5
{
"id":"haoren",
"name":"0",
"到期时间":"2020-01-01-00-00"
}

之后每次与服务器的交互行为都需要加上这个密钥,用来表示当前用户的身份证。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名

不再需要session保存状态了。

数据结构:

大概这个样子:

image-20200830211053072

分为三个部分:

  • header
  • claims
  • signature

之间用逗号做间隔,写成下面这个样子:

Header.Payload.Signature

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

在这里 你也可以自定义 key value值:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

这个部分的字段是没有经过加密的,重要信息不要往这里放

signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

这个算法大家看着肯定很不舒服,这其实就是base64的加密算法

特点总结

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

效果演示

创建项目之后,先导pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<properties>
<jjwt.version>0.7.0</jjwt.version>
<joda-time.version>2.9.6</joda-time.version>
</properties>

<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
</dependencies>

引用已经封装好的实体类,并且直接进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.ls.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;

/**
* @ClassName: JwtHelper
* @Description: token工具类
* @author: yuanxinqi
* @date: 2020/8/17 9:59
* @version: V 2.0.0
* @since: (jdk_1.8)
*/
public class JWTUtil {
/**
* 加密:生成token
*
* @param object
* @return
*/
public static String createToken(Object object, String key, int expireMinutes) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);

Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

//添加构成JWT的参数
JwtBuilder builder = Jwts.builder()
// .setHeaderParam("type", "JWT")
// .setSubject(userId.toString())
.claim(retuType(object), object) // 设置载荷信息
.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())// 设置超时时间
.signWith(signatureAlgorithm, signingKey);

//生成JWT
return builder.compact();
}

/**
* 解密:获取token中的参数
*
* @param token
* @return
*/
public static Claims parseToken(String token, String key) {
if ("".equals(token)) {
return null;
}

try {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(key))
.parseClaimsJws(token).getBody();
} catch (Exception ex) {
return null;
}
}


/**
* 将对象直接动态传入方法中,动态获取对象的名称
*
* @param object
* @return
*/
private static String retuType(Object object) {
String[] split = object.getClass().getName().split("\\.");
return split[split.length - 1];
}


public static void main(String[] args) throws Exception {
Dept asdf = new Dept(1, "尼玛死");
String token = JWTUtil.createToken(asdf, "asdfasdf", 30);

Claims asdf1 = JWTUtil.parseToken(token, "asdfasdf");
}
}

先打印生成的密钥

image-20200830213205512

我们先尝试将这个密钥暴力破解

打开这个网址 https://base64.supfree.net/,输入密钥

image-20200830213409122

发现跟我们预期的结果一致

至此,入门结束