JSON Web Token (JWT) - RFC 7519 的 Java 实现。
此库需要Java 8或更高版本。支持Java 7的最后一个版本为3.11.0。
安装
本库可以在 Maven 和 Bintray 上找到,Java API 文档在这里。
Maven
1 | <dependency> |
Gradle
1 | implementation 'com.auth0:java-jwt:3.19.0' |
可用算法
本库可以使用以下算法验证和签署 JWT:
JWS | Algorithm | Description |
---|---|---|
HS256 | HMAC256 | HMAC with SHA-256 |
HS384 | HMAC384 | 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 | ECDSA with curve P-384 and SHA-384 |
ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
⚠️ Note - ECDSA with curve secp256k1 and SHA-256 will not be supported for Java 15+ by this library since it has been (disabled in Java 15)[https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219]
用法
选择算法
算法定义了 Token 的签名和验证方式。在 HMAC 算法中可以使用 secret 的原始值实例化,在 RSA 和 ECDSA 算法中可以使用密钥对或 KeyProvider
。一旦创建,该实例可以重复使用于 Token 签名和验证等操作。
当使用 RSA 或 ECDSA 算法时,您只需要签署 JWTs,就可以通过传递空值来避免指定公钥。当您只需要验证 JWTs 时,也可以使用 Private Key 完成同样的操作。
使用静态 Secrets 或者 keys:
1 | //HMAC |
注:怎么获取或读取密钥不在本库介绍范围之内,想知道如何实现?可以参考这里
HMAC key 的长度和安全性
当使用基于哈希的消息认证码时,例如,HS256或HS512,为了遵守 JSON Web 算法(JWA)规范(RFC7518)的严格要求,必须使用与输出哈希大小具有相同(或更大)位长的密钥。这是为了避免削弱认证码的安全强度(参见NIST Recendations Nist SP 800-117)。例如,当使用HMAC256时,秘密密钥长度必须至少为256位。
使用 KeyProvider:
通过使用 KeyProvider,你可以在运行时更改签名 Token 的键,或者用 RSA 或 ECDSA 算法 去签名一个新 token。
这是通过实现 RSAKeyProvider
或 ECDSAKeyProvider
方法做到的:
getPublicKeyById(String kid)
: 在 Token 签名验证期间调用,返回用于验证 Token 的 key。如果轮值 key 被使用过了,它可以使用该 kid 获取到正确的轮值 key(或者只是返回相同的 key),类似于 JWK。getPrivateKey()
: 在 Token 签名期间调用,返回用于签署JWT的密钥。getPrivateKeyId()
: 在 Token 签名期间调用,返回 key(同getPrivateKey()
返回的 key) 的 id ,该值是 JWTCreator.Builder#withKeyId(String) 方法中设置的值的首选。如果你不需要设置kid
,请避免使用KeyProvider
去实例化一个算法。
接下来的例子展示了如何和 JwkStore
一起使用, 这是一个虚拟的JWK 实现。有关 JWKS 如何使用轮值 key ,请看 jwks-rsa-java 库。
1 | final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); |
创建和签名 Token
首先,调用 JWT.create()
方法并且传入 Algorithm
类型参数创建一个 JWTCreator
实例。然后,使用Builder定义你的 Token 需要的定制化的Claims。最后,通过调用 sign()
方法获取 String 类型的 token。
- 使用
HS256
的示例
1 | try { |
- 使用
RS256
的示例
1 | RSAPrivateKey privateKey = //Get the private key instance |
如果一个 Claim 无法被转换成 JSON,或者签名过程中使用的 Key 无效,会抛出 JWTCreationException
异常。
验证 Token
首先,调用 JWT.require()
方法并且传入 Algorithm
类型参数 获得 JWTVerifier
,如果你要求 Token 有特定的 Claim 值,可以使用Builder 去定义他们。方法 build()
返回的实例是可以重复使用的,所以定义一次之后就可以验证不同的 token。最后,调用 verifier.verify()
方法并且传入参数 token。
- 使用
HS256
的示例
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
- 使用
RS256
的示例
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
如果 Token 签名错误,或者 Claim 不符合要求,会抛出 JWTVerificationException
异常。
有效期验证
JWT Token 可能包含日期数值字段,可以通过以下方式验证:
Token 在过去日期发布
"iat" < TODAY
Token 尚未过期
"exp" > TODAY
- Token 目前可用
"nbf" < TODAY
验证令牌时,将自动发生时间验证,从而在值无效时抛出 jwtverificationException
异常。如果没有前缀属性,就会忽略这种验证。
要指定一个空余值(leeway window)让 Token 被认为有效,可以通过流式调用 JWTVerifier
的acceptLeeway()
方法并传入一个(正整数)秒数值。这适用于上面列出的每一项。
1 | JWTVerifier verifier = JWT.require(algorithm) |
你也可以为给定日期的 Claim 指定一个自定义值。并且覆盖该 Claim 的默认值。
1 | JWTVerifier verifier = JWT.require(algorithm) |
如果你需要在你的 lib/app 中测试此行为,可以将 Verification
实例转换为 BaseVerification
,然后就可以使用verification.build()
方法接收一个自动以的 Clock
入参。例如:
1 | BaseVerification verification = (BaseVerification) JWT.require(algorithm) |
解码 Token
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
如果 Token 不符合语法,或者 header/payload 不是 JSON,会抛出 JWTDecodeException
异常。
Header Claims
Algorithm (“alg”)
Returns the Algorithm value or null if it’s not defined in the Header.
返回设置的算法值,如果没在 Header 定义,则返回 null。
1 | String algorithm = jwt.getAlgorithm(); |
Type (“typ”)
返回设置的类型,如果没在 Header 定义,则返回 null。
1 | String type = jwt.getType(); |
Content Type (“cty”)
返回设置的内容,如果没在 Header 定义,则返回 null。
1 | String contentType = jwt.getContentType(); |
Key Id (“kid”)
返回设置的Key Id,如果没在 Header 定义,则返回 null。
1 | String keyId = jwt.getKeyId(); |
私有 Claims
在 Token 的 Header 部分定义的额外的 Claim,可以通过调用 getHeaderClaim()
方法并传入 Claim 名获取。该方法返回的值不会是 null,所以你可以通过 claim.isNull()
去检查一下。
1 | Claim claim = jwt.getHeaderClaim("owner"); |
当使用 JWT.create()
创建一个 Token 时,可以通过 withHeader()
方法(传入一个 claim 的Map集)指定 header 中的 Claims。
1 | Map<String, Object> headerClaims = new HashMap(); |
签名之后,
alg
和typ
值会一直包含在 Header 中。
载入 Claims
Issuer (“iss”)
返回发布人,如果没在 Payload 定义,则返回 null。
1 | String issuer = jwt.getIssuer(); |
Subject (“sub”)
返回主题,如果没在 Payload 定义,则返回 null。
1 | String subject = jwt.getSubject(); |
Audience (“aud”)
返回观众,如果没在 Payload 定义,则返回 null。
1 | List<String> audience = jwt.getAudience(); |
Expiration Time (“exp”)
返回到期时间,如果没在 Payload 定义,则返回 null。
1 | Date expiresAt = jwt.getExpiresAt(); |
Not Before (“nbf”)
返回 Not Before,如果没在 Payload 定义,则返回 null。
1 | Date notBefore = jwt.getNotBefore(); |
Issued At (“iat”)
返回发布时间,如果没在 Payload 定义,则返回 null。
1 | Date issuedAt = jwt.getIssuedAt(); |
JWT ID (“jti”)
返回 JWT ID,如果没在 Payload 定义,则返回 null。
1 | String id = jwt.getId(); |
Private Claims
可以通过 getClaims()
或者 getClaim()
获取定义在 Token Payload 中的额外 Claims。两个方法返回值不为空,所以可通过 claim.isNull()
去检查一下。
1 | Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name |
也可以这样写:
1 | Claim claim = jwt.getClaim("isAdmin"); |
当使用 JWT.create()
创建一个 Token 时,可以通过 withClaim()
方法(传入一个 claim 的Map集)指定一个自定义的 Claim。
1 | String token = JWT.create() |
你也可以调用 withPayload()
方法并且传入 Claim 的 Map集来创建一个 JWT:
1 | Map<String, Object> payloadClaims = new HashMap<>(); |
你也可以通过 JWT.require()
调用方法 withClaim()
来验证自定义的 Claims :
1 | JWTVerifier verifier = JWT.require(algorithm) |
目前支持的自定义JWT Claim 创建和验证的类型是:Boolean,Integer,Double,String,字符串和整数的日期和数组。
Claim 类型
Claim 类是 Claim 值的包装,它允许你将 Claim 作为不同的类型。对此有帮助的如下:
Primitives
- asBoolean(): 返回 Boolean 值或者 null.
- asInt(): 返回 Integer 值或者 null.
- asDouble(): 返回 Double 值或者 null.
- asLong(): 返回 Long 值或者 null.
- asString(): 返回 String 值或者 null.
- asDate(): 返回 Date 值或者 null。这里的返回值一定是数值类型的日期 (Unix Epoch/Timestamp). 请注意,JWT Standard 指定的是所有NumericDate值的单位都是秒。
自定义类和集合
要获取集合类型的 Claim,你需要提供一个类型值类让它转换:
- as(class): 返回被解析为指定类型的值。对于集合,应使用
asArray
或asList
方法。 - asMap(): 返回被解析为 Map
的值. - asArray(class): 返回被解析为指定类型的数组。如果值不是 JSON 数组,则返回 null.
- asList(class): 返回被解析为指定类型的 List, 如果值不是 JSON 数组,则返回 null.
如果值不能转换为给定的类型,会抛出 JWTDecodeException
异常。