Java开源工具库使用之JWT
官网介绍JWT 是什么?JSON Web Token(JWT)是一个开放标准(RFC7519),它定义了一种紧凑和独立的方式,可以作为 JSON 对象在各方之间安全地传输信息。这个信息可以被验证和信任,因为它是用数字签名完成的。jwt 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。虽然 JWT 也可以加密以提供各方之间的保密,但专注于签名 Token。
前言
官网介绍 翻译如下:
JWT 是什么?
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑和独立的方式,可以作为 JSON 对象在各方之间安全地传输信息。这个信息可以被验证和信任,因为它是用数字签名完成的。jwt 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
虽然 JWT 也可以加密以提供各方之间的保密,但专注于签名 Token 。签名 Token 可以验证其中包含的声明的完整性,而加密Token 可以向其他各方隐藏这些声明。当使用公钥/私钥对签名时,签名还证明只有持有私钥的一方是签名的一方。
什么时候使用 JWT?
授权:这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用JWT的一个特性,因为它的开销很小,并且可以轻松地跨不同的领域使用。
信息交换: JSON Web令牌是在双方之间安全传输信息的好方法。因为jwt可以被签名,例如,使用公钥/私钥对,所以您可以确定发件人就是他们所说的那个人。此外,由于签名是使用标头和有效负载来计算的,因此您还可以验证内容是否没有被篡改。
可以看到 JWT 常用于身份鉴权方面,它也是目前最流行的跨域身份验证解决方案。
传统的单机后端服务经常会使用 Session 来保存用户登录状态信息,但这种模式的问题是,遇到非浏览器如微信小程序,那不能使用Session 来保存用户信息,还有在多机(服务器集群)或面向服务的跨域体系的话,简单的 Session 就不能处理这种情况。
JWT 就是针对上述提供的一种灵活的解决方案,它是无状态的 Token,通过客户端保存数据,而服务器根本不保存会话数据,每个请求都被发送回服务器。
JWT 的实现库有很多种,针对不同编程语言有对应的库,具体可以见官网。java 也有好几种,官网排在最前面的是开源库 java-jwt。
github: https://github.com/auth0/java-jwt
pom 依赖如下:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.2</version>
</dependency>
一、JWT 规范
JWT 由三个部分: Header, Payload, Signature 组成,三部分用点(.)分开。
1.1 Header 头部
头部通常由两部分组成:Token 的类型,即 JWT,以及正在使用的签名算法,如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
这部分 json 数据通常会被 Base64 编码为一串字符串为 JWT 的头部,也就是第一部分,可通过 js 函数 btoa 实现
btoa('{"alg": "HS256","typ": "JWT"}')
得到
eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0=
1.2 Payload 载荷
这是 JWT 的核心部分,也是数据传输的主体,包含了要传输的数据,也有一些已经定义好的字段,可以称之为 Claims。
下面是一些常见的 Claims :
简称 | 全称 | 含义 |
---|---|---|
iss | Issuer | 发行方 |
sub | Subject | 主体 |
aud | Audience | (接收)目标方 |
exp | Expiration Time | 过期时间 |
nbf | Not Before | 早于该定义的时间的JWT 不能被接受处理 |
iat | Issued At | JWT 发行时的时间戳 |
jti | JWT ID | JWT 的唯一标识 |
例子:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1613297468168
}
同样也会被base64编码为字符串作为 JWT 的第二部分
btoa('{"sub": "1234567890","name": "John Doe","admin": true,"exp": 1613297468168}')
得到
eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiAiSm9obiBEb2UiLCJhZG1pbiI6IHRydWUsImV4cCI6IDE2MTMyOTc0NjgxNjh9
1.3 Signature 签名
签名的作用就是保护 Claims 的完整性(或者数据加密),保证 JWT 传输的过程中 Claims 不被篡改(或者不被破解)。
举个例子:使用HMAC SHA256 算法对头部和载荷用以下方式进行加密,就能得到签名。header 和 payload 都是明文经 base64 编码传输,通过签名来保证没有被篡改,即使得到明文部分,也知道对应的加密算法,但没有秘钥也很难伪造签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
二、Java 实现
2.1 创建
-
基本使用
使用 java-jwt 提供的 API 可以轻松生成 token
String userId = "10086"; LocalDateTime now = LocalDateTime.now(); long expireDays = 7; String s= ""; Date startDate = localDateTimeToDate(now); Date endDate = localDateTimeToDate(now.plusDays(expireDays)); Algorithm algorithm = Algorithm.HMAC256(s); Map<String, Object> header = new HashMap<>(); header.put("alg", "HS256"); header.put("typ", "JWT"); String token = JWT.create() .withHeader(header) .withAudience(userId) .withIssuedAt(startDate) .withExpiresAt(endDate) .withClaim("test", "test values") .sign(algorithm); System.out.println(token); // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxMDA4NiIsInRlc3QiOiJ0ZXN0IHZhbHVlcyIsImV4cCI6MTY3NDU2NzAxMSwiaWF0IjoxNjczOTYyMjExfQ.GSpwAX6WG3RMplu4TdSsYLWTjNlqGh3KC-ifhA-_x50
-
更换加密算法
java-jwt 内置了非常多的加密算法,例如:可以使用 RSA256
KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA"); rsa.initialize(512); KeyPair keyPair = rsa.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); System.out.println(Base64.getEncoder().encodeToString(publicKey.getEncoded())); System.out.println(Base64.getEncoder().encodeToString(privateKey.getEncoded())); Algorithm rsa256Algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, (RSAPrivateKey) privateKey);
2.2 解密
DecodedJWT jwt = JWT.require(algorithm).build().verify(token);
String jwtAlgorithm = jwt.getAlgorithm();
String jwtId = jwt.getAudience().get(0);
Map<String, Claim> claims = jwt.getClaims();
System.out.printf("%s \n%s \n%s%n", jwtAlgorithm, jwtId, claims);
2.3 校验
-
时间校验
JWT token可以包含一些用于验证它的日期相关信息:
- 该token是已发布的token: “iat” < NOW
- 该token还没有过期: “exp” > NOW
- 该token已经被使用:“nbf” < NOW
对日期的验证是在验证token时自动执行的,如果验证不通过则会抛出 JWTVerificationException 异常。
验证方法中 acceptLeeway 这个方法是设置 leeway 这个属性的,通过查看源码可以发现 是留有余地的秒值
private boolean assertInstantIsFuture(Instant claimVal, long leeway, Instant now) { return claimVal == null || !now.minus(Duration.ofSeconds(leeway)).isAfter(claimVal); } private boolean assertInstantIsPast(Instant claimVal, long leeway, Instant now) { return claimVal == null || !now.plus(Duration.ofSeconds(leeway)).isBefore(claimVal); }
JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) .build(); verifier.verify(token);
-
claim 校验
除了上述的时间校验,还可以校验传递的信息,即 claim,通不过就抛异常 JWTVerificationException
JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) .withClaim("test", "123") .withClaimPresence("test") .build(); verifier.verify(token);
参考
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)