JWT详解和使用(jjwt)
编辑JWT详解和使用
JWT是啥
JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
下列场景中使用JSON Web Token是很有用的:
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
- Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
JWT怎么用
在登录时,服务器端获取用户的信息等,根据需求(如expiration等)生成一个JWT返回给客户端。客户端每次请求时,带上该JWT,服务器端只需要对该JWT进行解密,就可以得知该JWT是否有效、用户的信息(注意不能有敏感信息)等。这个过程服务端不需要存储JWT的内容(不同于之前类似Session id的那种token),只需要对JWT进行加解密操作。
ps.之前我使用的类似Session id的token是指用随机生成的字符串作为token(同时作为缓存的key),将用户信息json化(或其他序列化方式)缓存。token返回给客户端,客户端每次请求时带上token,服务器就可以从缓存中读出用户信息,减少了数据库的压力(?)。
JWT的结构
如图,JWT由三个部分组成,之间由一个“.”连接
- Header/头部(我来组成头部x)
- Payload/载荷
- Signature/签名
下面具体介绍这三个部分
Header/头部
头部一般由两个部分组成,token的类型和使用的算法。形式如下:
{
"alg":"HS256",
"typ":"JWT",
"zip":"...",
"YOUR_KEY":"YOUR_VALUE"
}
当然还可以增加一些如zip(指示压缩方法)等自定义的字段。
Payload(Body)/载荷
payload部分是JWT存储信息的部分,包含着Claims(声明),其实就是存储的的数据。
一般声明分为以下三种类型:
- Registered claims:预定义的声明,如:
- iss:issuer 发布者的URL地址
- sub:subject JWT面向的用户,不常用
- aud:audience 接受者的URL地址
- exp:expiration JWT失效的时间(Unix timestamp)
- nbf:not before 该时间前JWT无效(Unix timestamp)
- iat:issued at JWT发布时间(Unix timestamp)
- jti:JWT ID
- Public claims:公开字段(也不知道干啥的)
- Private claims:自定义私有字段(可以在这个字段里定义要传递的用户信息等)不能在此字段传递加密的信息,这部分采用对称加密方式,传输内容可以被解开。
eg.
{
"sub":"1234567890",
"name":"John Doe",
"admin":true
}
Signature/签名
签名部分的生成公式如下
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
即base64编码的header和payload,加上一个秘钥。
签名的用途显而易见就是验证前面部分的内容是否有被篡改(因为前面是对称加密的,容易修改)。
使用jjwt生成JWT
这里使用了一个开源项目jjwt(https://github.com/jwtk/jjwt)来实现。当然JWT的实现也不难,这里因为懒(以及菜)就选择用这个开源项目了。
Installation/部署
首先需要在Maven中添加dependencies,如下
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
我使用的环境是IDEA,设置了Auto-import,所以添加了之后稍等一下就发现红线消失,依赖问题解决了。
Maven真好用 IDEA真聪明(小声逼逼
Quickstart/快车
一个JWS(signed JWT)的创建过程大致分为以下三步:
- 构建一个有默认载荷的JWT
- 用秘钥签名这个JWT(秘钥必须满足HMAC-SHA-256算法)
- 打包JWT成一个字符串
贴一下代码
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();
注意到第8行,这里用助手函数生成了一个随机的满足加密算法要求的key,在正常使用中,需要自己定义一个秘钥。
输出会得到一个类似如下的字符串,这个就是生成的JWT,可以看到Header、Payload和Signature三个部分
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
那么如何验证这个JWT是否有效呢
贴代码
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws);
//OK, we can trust this JWT
} catch (JwtException e) {
//don't trust the JWT!
}
OK,JWT生成、验证的过程就这么简单!
接下来开始正式使用部分的介绍
生成JWT
先贴代码
byte[] secret = "2162d3e65a421bc0ac76ae5acfe29c650becb73f2a9b8ce57941036331b1aaa8".getBytes();
SecretKey key = Keys.hmacShaKeyFor(secret);
String jws = Jwts.builder()
.setHeaderParam("kid", "123456")
.setSubject("111")
.setIssuer("ameow")
.setNotBefore(new Date())
.claim("weisha", "wozhidaole")
.signWith(key)
.compact();
首先是我比较关心的key的部分。查阅文档(https://github.com/jwtk/jjwt )可以知道,JWT对key的长度是有要求的,以这里SHA-256为例,就需要256位的key。具体加密方法对应的key要求可以在文档中查到,不多叙述(因为不会)。
这里我想选择一个字符串“hello”作为key,于是我需要生成一个对应的SHA-256加密的串,这里可以用Java实现SHA-256加密,也可以使用在线的SHA-256生成工具直接生成这个串,然后直接换成byte形式,用jjwt提供的助手方法转换成key。(可能这样会不太安全?)
然后就是JWS的部分,用Jwts.builder(),然后就是set里面的内容,最后sign和compact,就可以得到生成的JWT。
如果要生成自定义的claim,可以采用以下的方法
Claims claims = Jwts.claims();
populate(claims); //implement me
String jws = Jwts.builder()
.setClaims(claims)
// ... etc ...
其他部分暂时还没有接触到,碰到再研究。
读取JWT
简单几步
- 用Jwts.parser()创建一个JwtParser对象
- 指定要使用的秘钥setSigningKey()
- 调用parseClaimsJws()方法
- 用try/catch块包裹这部分内容,方便jjwt处理异常
如果要获取里面的内容,可以采用以下代码
Jws<Claims> jwsR;
try {
jwsR = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jws);
System.out.println(jwsR);
System.out.println(jwsR.getBody().get("weisha"));
} catch (JwtException ex) {
System.out.println("???");
}
输出如下(1是传入的JWT,2是JWT解释出的内容,3是获取到的Body中的weisha字段中的内容)
eyJraWQiOiIxMjM0NTYiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMTEiLCJpc3MiOiJhbWVvdyIsIm5iZiI6MTU3MTEyMjI4MCwid2Vpc2hhIjoid296aGlkYW9sZSJ9.nOvpsOZ93bmdjBgRRADoWJhTAJ-QOrumHtFgqCd6V9CAafZe0nzXCBxbw2YmsVKLW2i2SGy0FbgZjBtt_H8Q3w
header={kid=123456, alg=HS512},body={sub=111, iss=ameow, nbf=1571122280, weisha=wozhidaole},signature=nOvpsOZ93bmdjBgRRADoWJhTAJ-QOrumHtFgqCd6V9CAafZe0nzXCBxbw2YmsVKLW2i2SGy0FbgZjBtt_H8Q3w
wozhidaole
最后
这篇博客没有提到加密方面的细节,是因为我对安全方面不太了解,这方面还有待学习。如果有安全方面需求,可以查阅以下的ref。
Ref:
- 0
- 0
-
赞助
微信赞赏码 -
分享