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的tokenalg标识签名算法。

格式为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一旦下发,除非自动过期,不能手动使其失效,且续期难以实现

优点:

  • 不需要在服务端存储数据,每次请求完全是无状态的,这使得服务端的实现更加简单
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。