一、前言
Java 编程中使用 jwt, 首先你必须了解 jwt 是什么,长什么样子。如果你不了解可以先去本站另外一篇博客什么是 JWT?
二、Java 编程中 jwt 框架选择
在 Java 编程中, 实现 jwt 标准的有很多框架,本博客采用的框架是 auth0 的 java-jwt 版本为 3.2.0
三、什么是 Java-JWT
auth0 的 java-jwt 是一个 JSON WEB TOKEN(JWT)的一个实现。
四、安装下载相关依赖
如果你是采用 maven 的方式, 在你的项目 pom.xml 文件中添加以下 java-jwt 的依赖片段:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
如果你是采用 Gradle 的方式, 则添加以下内容:
compile 'com.auth0:java-jwt:3.2.0'
五、java-jwt 已经实现的算法
该库使用以下算法实现 JWT 验证和签名:
JWS | 算法 | 介绍 |
---|---|---|
HS256 | HMAC256 | HMAC with SHA-256 |
HS384 | HMAC512 | HMAC with SHA-384 |
HS512 | HMAC512 | HMAC with SHA-512 |
RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
ES384 | ECDSA384 | ECDSA384 ECDSA with curve P-384 and SHA-384 |
ES512 | ECDSA512 | ECDSA512 ECDSA with curve P-521 and SHA-512 |
六、如何使用 java-jwt
6.1. 选择一种算法
算法定义了一个令牌是如何被签名和验证的。它可以用 HMAC 算法的原始值来实例化,也可以在 RSA 和 ECDSA 算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。
在使用 RSA 或 ECDSA 算法时,只需要签署 JWTs,就可以通过传递 null 值来避免指定公钥。当您需要验证 JWTs 时,也可以使用私钥进行操作
使用静态的字符密文或者 key 来获取算法器:
//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");
//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
使用一个 key 提供者来获取算法:
通过使用 KeyProvider,您可以在运行时更改密钥,用于验证令牌签名或为 RSA 或 ECDSA 算法签署一个新的令牌。这是通过实现 RSAKeyProvider 或 ECDSAKeyProvider 方法实现的:
- getPublicKeyById(String kid): 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如 JWK,它可以使用 id 来获取正确的轮换键 (或者只是一直返回相同的键)。
- getPrivateKey(): 在令牌签名期间调用它,它应该返回用于签署 JWT 的密钥。
- getPrivateKeyId(): 在令牌签名期间调用它,它应该返回标识由 getPrivateKey() 返回的键的 id 的 id。这个值比 JWTCreator.Builder 和 keyid(String)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用 KeyProvider 实例化算法。
下面的代码片段将展示如何使用:
final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");
final RSAPrivateKey privateKey = //Get the key instance
final String privateKeyId = //Create an Id for the above key
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
//Received 'kid' value might be null if it wasn't defined in the Token's header
RSAPublicKey publicKey = jwkStore.get(kid);
return (RSAPublicKey) publicKey;
}
@Override
public RSAPrivateKey getPrivateKey() {return privateKey;}
@Override
public String getPrivateKeyId() {return privateKeyId;}
};
Algorithm algorithm = Algorithm.RSA256(keyProvider);
//Use the Algorithm to create and verify JWTs.
提示: 对于使用 JWKs 的简单的键轮换,可以尝试 JWKs-rsa-java 库。
6.2. 创建一个签名的 JWT token
首先需要通过调用 jwt.create()创建一个 JWTCreator 实例
例如使用 HS256 算法:
try {Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (UnsupportedEncodingException exception){//UTF-8 encoding not supported} catch (JWTCreationException exception){//Invalid Signing configuration / Couldn't convert Claims.}
例如使用 RS256 算法:
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){//Invalid Signing configuration / Couldn't convert Claims.}
如果 Claim 不能转换为 JSON,或者在签名过程中使用的密钥无效,那么将会抛出 JWTCreationException 异常。
6.3. 验证令牌
首先需要通过调用 jwt.require() 和传递算法实例来创建一个 JWTVerifier 实例。如果您要求令牌具有特定的 Claim 值,请使用构建器来定义它们。方法 build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。最后调用 verifier.verify()来验证 token
例如使用 HS256 算法的时候:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){//UTF-8 encoding not supported} catch (JWTVerificationException exception){//Invalid signature/claims}
例如使用 RS256 算法的时候:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){//Invalid signature/claims}
如果令牌有一个无效的签名,或者没有满足 Claim 要求,那么将会抛出 JWTVerificationException 异常
6.4.jwt 时间的验证
JWT 令牌可能包括可用于验证的 DateNumber 字段:
- 这个令牌发布了一个过期的时间 “iat” < TODAY
- 这个令牌还没过期 “exp” > TODAY and
- 这个令牌已经被使用了. “nbf” > TODAY
当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个 JWTVerificationException。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。
要指定令牌仍然被认为有效的余地窗口,在 JWTVerifier builder 中使用 accept 回旋 () 方法,并传递一个正值的秒值。这适用于上面列出的每一项。
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 sec for nbf, iat and exp
.build();
您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) //1 sec for nbf and iat
.acceptExpiresAt(5) //5 secs for exp
.build();
如果您需要在您的 lib/app 中测试此行为,将验证实例转换为 basever 可视化,以获得 verific.build()方法的可见性,该方法可以接受定制的时钟。例如:
BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //Must implement Clock interface
JWTVerifier verifier = verification.build(clock);
6.5 解码一个 jwt 令牌
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){//Invalid token}
如果令牌有无效的语法,或者消息头或有效负载不是 JSONs,那么将会抛出 JWTDecodeException 异常。
6.6JWT 头部解析
Algorithm (“alg”)
返回 jwt 的算法值或, 如果没有定义则返回 null
String algorithm = jwt.getAlgorithm();
Type (“typ”)
返回 jwt 的类型值,如果没有定义则返回 null(多数情况类型值为 jwt)
String type = jwt.getType();
Content Type (“cty”)
返回内容的类型, 如果没有定义则返回 null
String contentType = jwt.getContentType();
Key Id (“kid”)
返回 key 的 id 值, 如果没有定义则返回 null
String keyId = jwt.getKeyId();
私有的 Claims, 即自定义字段
在令牌的头部中定义的附加声明可以通过调用 getHeaderClaim() 获取, 即使无法找到,也会返回。您可以通过调用 claim.isNull()来检查声明的值是否为 null。
Claim claim = jwt.getHeaderClaim("owner");
当使用 jwt.create()创建一个令牌时,您可以通过调用 withHeader()来指定头声明,并同时传递声明的映射。
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);
提示: 在签名过程之后,alg 和 typ 值将始终包含在 Header 中。
6.7JWT 的负载 (Payload) 声明
Issuer (“iss”)
返回签发者的名称值, 如果没有在负载中定义则返回 null
String issuer = jwt.getIssuer();
Subject ("sub")
返回 jwt 所面向的用户的值, 如果没有在负载中定义则返回 null
String subject = jwt.getSubject();
Audience ("aud")
返回该 jwt 由谁接收, 如果没有在负载中定义则返回 null
List<String> audience = jwt.getAudience();
Expiration Time ("exp")
返回该 jwt 的过期时间, 如果在负载中没有定义则返回 null
Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")
Returns the Not Before value or null if it’s not defined in the Payload.
Date notBefore = jwt.getNotBefore();
Issued At ("iat")
返回在什么时候签发的, 如果在负载中没有定义则返回 null
Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")
返回该 jwt 的唯一标志, 如果在负载中没有定义则返回 null
String id = jwt.getId();
自定义声明
在令牌有效负载中定义的附加声明可以通过调用 getClaims()或 getClaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用 claim.isNull()来检查声明的值是否为 null。
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name
Claim claim = claims.get("isAdmin");
或者:
Claim claim = jwt.getClaim("isAdmin");
当使用 jwt.create()创建一个令牌时,您可以通过调用 withClaim()来指定自定义声明,并同时传递名称和值。
String token = JWT.create()
.withClaim("name", 123)
.withArrayClaim("array", new Integer[]{1, 2, 3})
.sign(algorithm);
您还可以通过调用 withClaim()来验证 jwt.require()的自定义声明,并传递该名称和所需的值。
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("name", 123)
.withArrayClaim("array", 1, 2, 3)
.build();
DecodedJWT jwt = verifier.verify("my.jwt.token");
提示: 当前支持的自定义 JWT 声明创建和验证的类型是:Boolean, Integer, Double, String, Date 和 Arrays。
6.8Claim Class
索赔类是索赔值的包装器。它允许您将索赔作为不同的类类型。可用的工具方法:
原始的:
- asBoolean(): 返回布尔值, 如果不能转换返回 null。
- asInt(): 返回整数值, 如果不能转换返回 null。
- asDouble(): 返回 Double 值, 如果不能转换则返回 null。
- asLong(): 返回 Long 值,如果不能转换则返回 null。
- asString(): 返回 String 值, 如果不能转换则返回 null。
- asDate(): 返回 Date 值, 如果不能转换则返回 null。 必须是一个数字日期 (Unix 系统时间戳). 注意,JWT 标准指定所有的数字日期值必须以秒为单位。
自定义类型和集合:
要获得作为集合的声明,您需要提供要转换的内容的类类型
- as(class): 返回 Class Type 的解析值. 对于集合,您应该使用 asArray 和 asList 方法。
- asMap(): 返回被转换为 Map<String, Object> 的值
- asArray(class): 返回被转换成元素类型的 Class Type, or null if the value isn’t a JSON Array.
- asList(class): 返回集合元素的 Class Type, or null if the value isn’t a JSON Array.
如果不能将值转换为给定的类类型,则会抛出 JWTDecodeException 异常
- 6.1. 选择一种算法
- 6.2. 创建一个签名的 JWT token
- 6.3. 验证令牌
- 6.4.jwt 时间的验证
- 6.5 解码一个 jwt 令牌
- 6.6JWT 头部解析
- Algorithm (“alg”)
- 私有的 Claims, 即自定义字段
- 6.7JWT 的负载(Payload) 声明
- 6.8Claim Class
- 自定义类型和集合:
- 6.1. 选择一种算法
- 6.2. 创建一个签名的 JWT token
- 6.3. 验证令牌
- 6.4.jwt 时间的验证
- 6.5 解码一个 jwt 令牌
- 6.6JWT 头部解析
- Algorithm (“alg”)
- 私有的 Claims, 即自定义字段
- 6.7JWT 的负载 (Payload) 声明
- 6.8Claim Class
- 自定义类型和集合: