JWT身份认证
JWT(Json Web Token)常用于基于HTTP协议的接口认证,JWT本身是一种加密的凭证,数据全部存储在凭证中,是相对于服务端Session + 客户端SessionID的另一种认证和会话数据存储方式。JWT是一种规范,我们可以查阅RFC7519。
JWT和Session区别
基于Session的认证中,服务端返回一个sessionID作为cookie存储在客户端,用户相关的信息以及是否登录等字段,都存储在服务端,存储的键我们一般称为sessionID。
基于JWT的认证,则是将所有信息存储在token中,服务端对token进行了签名,因此客户端无法篡改其内容。token在用户名和密码认证成功后即下发到客户端,客户端请求时则一般是将其带在HTTP请求头中。

注:一个常见的误解是有些声称使用JWT认证的开发人员仍同时使用Redis保存用户的状态信息,并给用户签发一个JWT和Redis中的状态数据进行关联,这是个假的、多此一举的JWT,本质还是基于Session的认证,使用JWT后服务端的实现一定是无状态的。
JWT数据格式
JWT约定了一个token数据格式,由三部分组成:
- 头部 Header
- 载荷 Payload
- 签名 Signature
三部分由点号.连接,形如Base64(Header).Base64(Payload).Signature。
下面是一个JWT的例子。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InRlc3QiLCJleHAiOjE1OTU4NjMyOTYsImVtYWlsIjoidGVzdEBnbWFpbC5jb20iLCJvcmlnX2lhdCI6MTU5NTgzNDQ5Nn0.SwLv5sj3Bg7I9cTYzFifSe6DiR-EzbbH4MNMzP_zOAE
头部 Header
头部描述JWT的基本信息,比如使用的算法等,例子:
{
"typ":"JWT",
"alg":"HS256"
}
其中,typ标识该数据是一个JWT的token,alg标识签名算法。
格式为Json,在token中是使用Base64编码的明文数据。
载荷 Payload
{
"user_id":2,
"username":"test",
"exp":1595863296,
"email":"test@gmail.com",
"orig_iat":1595834496
}
和Header一样,格式为Json,在token中是使用Base64编码的明文数据。
签名 Signature
签名是对Header和Payload的签名,签名算法在Header中标识,JWT支持多种算法,比较常用的是HMAC系列。签名密钥由服务端保管,签名的目的是防篡改(如果不签名,客户端可以篡改payload,就能够随意切换成其它用户了)。
使用HTTP请求头传递JWT
具体调用需要认证的接口时,如何将token传递给服务端,这个和服务端实现相关。一般来说,常见的方式是以如下格式的HTTP请求头来传递。
Authorization: Bearer <token>
这样设置是因为w3c规定了用于传递认证信息的Authorization请求头格式形如Authorization: <type> <authorization-parameters>。
Java中的JWT实现
JWT并不是什么复杂的格式,各种语言、Web框架等基本都有JWT的扩展库,我们自己造轮子也不是十分困难。Java中,我们可以直接使用java-jwt这个第三方库。
引入Maven依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.1.0</version>
</dependency>
下面代码中,我们使用该库实现了JWT的生成和验证解析。
// 生成token
String secret = "abc123";
Algorithm algorithm = Algorithm.HMAC256(secret);
Map<String, String> payload = new HashMap<>();
payload.put("user_id", "2");
payload.put("username", "test");
payload.put("email", "test@gmail.com");
String token = JWT.create().withPayload(payload).sign(algorithm);
// 验证并解析token
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
代码非常简单,这里我们使用的是HMAC256算法,其中secret参数是服务端的密钥,加解密都需要该密钥;payload是我们要存储在JWT中的信息。这里要注意的是解析后JWT的payload是个字符串类型,它是一段Base64编码的文本,我们可以将其解码得到Json数据。
String s = new String(Base64.getDecoder().decode(decodedJWT.getPayload()));
JWT的优缺点
最后,我们总结下JWT相比于Session方式的优缺点。
缺点:
- 载荷信息明文存储在客户端,网络被监听时有一定隐患,当然我们也可以额外对Payload进行加密规避这个问题,不过目前并没有一个被广泛接受的对Payload加密的统一标准
- 载荷信息可能会很大,频繁访问会造成不必要的网络开销
- JWT一旦下发,除非自动过期,不能手动使其失效,且续期难以实现
优点:
- 不需要在服务端存储数据,每次请求完全是无状态的,这使得服务端的实现更加简单