JSON Web Token

JWT:

什么是JWT?

JSON Web Token(JWT) 是一种开放式标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象安全传输信息。这些信息可以通过数字签名进行验证和信任。可以使用加密(使用HMAC算法)或使用RSA的公钥/私钥对JWT进行签名。

以上是我百度的,看起来真累,到底什么是JWT?

传统方式:

基本上所有的前后端登录验证交互这一块都会遵循这样一个流程:

客户端登录验证

最早的时候,web端和服务端的交互,我们使用session。后来前后端分离后,通过restful进行数据交互,同样使用类似于上面流程图的交互方式:前端登录,后端根据用户信息生成一个token,并保存这个token和对应的用户信息到关系型数据库,或者缓存数据库redis,接着将token传递给客户端,客户端会存入浏览器cookie。后续http请求都会携带这个token,后端根据token来查询、验证用户的信息。

单体应用或者用户量少的时候,一切都没有什么问题。随着互联网的高速发展,用户数量的暴增,就开始面临一系列的问题:

1:后端每次都要根据token查出用户信息,这样就增加了数据层的查询和存储开销。把验证信息保存在session中,加大了服务器端的存储压力。

2:后端采用分布式服务之后,session的共享问题,也是一个非常棘手的问题。

3:数据的安全性产生影响(cookie的泄露)。

虽然以上问题都有各种各样的解决方案,但都不尽人意。有没有一种方式,不用去服务器查询呢?如果我们生成的token遵循一定的规律。比如我们使用加密算法,根据用户的登录信息,以及一个一开始定义好的全局密码生成一个token,这样只需要解密该token就可以知道用户的信息了。

可以使用JWT。

JWT:

特点:

简洁:可以通过url,post参数或者在HTTP header中发送。说白了,通过JWT生成的token就是一个JSON字符串,因为数据量小,传输速度快。

自包含:Payload中包含了用户的信息,避免了多次查询数据库。

JWT详情

上面这个图片就是JWT的组成部分:

Header:头部,包含了两部分:token类型(JWT)和采用的加密算法(HS256)。

PayLoad:负载,这部分是我们存放信息的地方,你可以把用户的id,name等信息放在这里。JWT规范里有对这部分信息进行了比较详细的介绍,常用的有:iss(签发者),exp(过期时间),sub(面向的用户)等信息。

前面这两部分都是通过Base64进行编码组成的。这两部分并没有经过加密处理,所以可以认为内容是公开的。所以一定不要将用户密码等敏感信息放入。

Signature:签名。Siguature需要使用编码后的header和payload以及我们提供的一个秘钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过。

三个部分通过.连接在一起就是我们需要的JWT了,它大概长这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这样一个完整的token就生成了。而在实际的项目中JWT的运用流程是这样的:

1:用户首次登录,输入用户名和密码,后端首先去数据库查询出用户的信息,如果用户名和密码信息无误,则根据用户的userId或者name或者我们其他我们想要放入的信息根据上面定义好的算法,再加上后台应用中提供的密钥生成一个token。将token返回给客户端,并提示登录成功。

2:随后客户端会将token存入浏览器cookie或者Local Storage中。

3:之后客户端每次向服务端发送请求都会携带这个token,服务端可以解密token验证用户的合法性,获取用户的信息而不用每次都去数据库查询了。

说的再多都是浮云,一篇小小的demo就能说明一切(本次我们使用第三方针对Java的一个开源JSON Web Token:jjwt):

我们就使用官方提供的最简单的例子:

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

/**
* @author JiaShun
* @date 2018/4/29 23:57
*/
@Component
public class JWTUtil {
/**
* 根据一个String字符串生成一个密钥key,这个字符串只有应用层自己知道,千万不能泄露,建议经常修改。
* 这个key相当于你所有用户所公有的一个用户密码。
*/
public SecretKey generalKey() {
String stringKey = "helloWorld";
byte[] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key = new SecretKeySpec(encodedKey,0,encodedKey.length,"AES");
return key;
}

/**
* 创建JWT
* @param subject 用户详细信息
* @return
* @throws Exception
*/
public String createJWT(String subject)throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey key = generalKey();
JwtBuilder builder = Jwts.builder()
.setSubject(subject)
.signWith(signatureAlgorithm,key);
return builder.compact();
}

/**
* 解析JWT
* @param jwt
* @return
* @throws Exception
*/
public Claims parseJWT(String jwt)throws Exception {
SecretKey key = generalKey();
return Jwts.parser().setSigningKey(key).parseClaimsJws(jwt).getBody();
}

}

代码就这么简单!

下面我们做一个单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringRunner.class)
@SpringBootTest
public class TokenApplicationTests {

@Autowired
private JWTUtil jwtUtil;

@Test
public void contextLoads() {
}

@Test
public void testCreateJWT()throws Exception {
JSONObject json = new JSONObject();
json.put("userId","1");
json.put("name","JiaShun");
String subject = json.toJSONString();
String token = jwtUtil.createJWT(subject);
System.out.println(token);
}

}

输出的结果为:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJuYW1lXCI6XCJKaWFTaHVuXCIsXCJ1c2VySWRcIjpcIjFcIn0ifQ.NqYVyNM2XT_2hKkgTakPf0I6ira00IDANGpZTTLJi_8

这就是一个完整的JSON Web Token。

随后我们解析这个token,看得到什么信息:

1
2
3
4
5
6
7
@Test
public void testParseJWT()throws Exception {
Claims claims = jwtUtil.parseJWT("eyJhbGciOiJIUzI1NiJ9" +
".eyJzdWIiOiJ7XCJuYW1lXCI6XCJKaWFTaHVuXCIsXCJ1c2VySWRcIjpcIjFcIn0ifQ" +
".NqYVyNM2XT_2hKkgTakPf0I6ira00IDANGpZTTLJi_8");
System.out.println(claims);
}

打印出的信息为:

{sub={“name”:”JiaShun”,”userId”:”1”}}

这样我们就得到了用户的所有信息,而不用去数据层反复查询请求的真实性了。

快掏出你的大手机扫我

快掏出你的大手机扫我