JJWT
目录
JWT 的声明一般被用来在客户端和服务端间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑信息
¶概述
JJWT 旨在成为最易使用和理解的库,用于在 JVM 和 Android 上创建和验证 JSON Web 令牌,JWT
本身是支持加密签名的,在使用签名的JWT
时,需要注意一下两点:
真实性
和完整性
保证JWT
包含可以信任的信息。 如果JWT
未通过真实性或完整性检查,应该始终拒绝JWT
,因为我们无法信任它
¶Hello World
仍然按照常见的套路(依赖,HelloWorld,详细概念)三步骤学习一个第三方库
¶依赖
1 | <dependency> |
¶创建 JWT
1 | public static void main(String[] args) { |
上述代码中,构建了一个主题为pineapple-man
的 JWT,使用的是HMAC-SHA-256
算法的密钥对 JWT 进行签名,并在最终将它压缩成 String 形式
¶解析 JWT
1 | public static void main(String[] args) { |
将之前的密钥用于验证JWT
的签名,如果它无法验证JWT
,则抛出SignatureException
。如果在做 JWT 解析时,发生了验证失败的现象,可以通过捕获JwtException
异常来自定义失败情况下的处理
1 | try { |
¶JWS
JWS 就是已经签名的 JWT,下面展示了如何手动实现 JWS
1 | //头部信息 |
以上就是 JWS(已经签名的 JWT)生成过程的实现方式
¶创建 JWS
可以使用这种方式创建JWS
:
- 使用
Jwts.builder()
方法创建JwtBuilder
实例 - 调用
JwtBuilder
方法根据需要添加标头参数和声明 - 指定要用于对
JWT
进行签名的SecretKey
或非对称PrivateKey
- 调用
compact()
方法进行压缩和签名,生成最终的jws
1 | String jws = Jwts.builder() // (1) |
¶设置 Header Parameters
JWT Header
提供关于JWT Claims
相关的内容,格式和加密操作的元数据
如果需要设置一个或多个JWT
头参数,则可以根据需要简单地多次调用JwtBuilder.setHeaderParam
1 | String jws = Jwts.builder() |
每次调用setHeaderParam
时,它只是将键值对附加到内部Header
实例,如果键值已经存在,则会覆盖任何现有的同名键/值对
除此之外,还可以使用其他方式,设置JWT Header
,由于第一种方式能够完成常见的业务场景,其余的方式这里并不阐述
¶设置 Claims
Claims
是JWT
的正文部分,包含JWT
创建者希望向JWT
收件人提供的信息,常见的 API 如下
API | 含义 |
---|---|
setIssuer | sets the iss (Issuer) Claim |
setSubject | sets the sub (Subject) Claim |
setAudience | sets the aud (Audience) Claim |
setExpiration | sets the exp (Expiration Time) Claim |
setNotBefore | sets the nbf (Not Before) Claim |
setIssuedAt | sets the iat (Issued At) Claim |
setId | sets the jti (JWT ID) Claim |
1 | String jws = Jwts.builder() |
当然也可以自定义 Claims,如果需要设置一个或多个与上面显示的标准 setter 方法声明不匹配的自定义声明,可以根据需要多次调用JwtBuilde.claim
声明:
1 | String jws = Jwts.builder() |
每次调用claim
时,它只是将键值对附加到内部claims
实例,如果键值已经存在,则会覆盖任何现有的同名key/value
对,同头部信息一样
¶签名
JWT 规范确定了 12 种标准签名算法,三种密钥算法和九种非对称密钥算法,由于也不是做密码学的这里也不过多展开。自己项目中常见的使用HMAC-SHA-256
就足够了
👴建议通过调用JwtBuilder
的signWith
方法来指定签名密钥,以及对应的摘要算法,示例如下:
1 | String jws = Jwts.builder() |
使用signWith
时,JJWT
还会自动使用相关的算法标识符设置所需的alg
头。类似地,如果使用长度为 4096 位的RSA PrivateKey
调用signWith
,JJWT
将使用RS512
算法并自动将alg
头设置为RS512
¶解析 JWS
⛵解析 JWS 步骤如下:
- 使用
Jwts.parser()
方法创建JwtParser
实例 - 指定要用于验证
JWS
签名的SecretKey
或非对称PublicKey
- 调用
parseClaimsJws(String)
方法,生成原始JWS
🎶如之前所述,整个调用将包装在try/catch
块中,以防解析或签名验证失败
1 | Jws<Claims> jws; |
¶校验 Key
阅读JWS
时,最重要的事情是指定用于验证 JWS 加密签名的密钥。 如果签名验证失败,则无法安全地信任此JWT
,应将其丢弃。如果jws
是使用SecretKey
签名的,则应在JwtParser
上指定相同的SecretKey
1 | Jwts.parser() |
如果jws
是使用PrivateKey
签名的,那么应该在JwtParser
上指定该密钥相应的PublicKey
(不是PrivateKey
)
1 | Jwts.parser() |
❓如果应用程序不止使用一个 SecretKey 或 KeyPair 会怎么样? 如果可以使用不同的 SecretKeys 或公钥/私钥或两者的组合创建 JWS,该怎么办?
1 | //实现了 SigningKeyResolver 接口的自定义解析类 |
通过实现SigningKeyResolverAdapter
接口的resolveSigningKey(JwsHeader,Claims)
方法来简化一些事情
1 | public class MySigningKeyResolver extends SigningKeyResolverAdapter { |
在解析JWS JSON
之后,JwtParser
将在验证jws
签名之前调用resolveSigningKey()
方法。 这也就允许检查Jws Header
和Claims
参数,以帮助查找用于验证特定jws
的密钥的信息。 这对于复杂安全模型的应用程序非常强大,这些安全模型可能在不同时间使用不同的密钥或针对不同的用户或客。JWT
规范支持的方法是在创建JWS
时,在JWS
头中设置kid(Key ID)
字段,例如:
1 | Key signingKey = getSigningKey(); |
然后在解析期间,SigningKeyResolver
可以检查JwsHeader
以获取该kid
,然后使用该值从某个位置查找密钥,如数据库。 例如:
1 | public class MySigningKeyResolver extends SigningKeyResolverAdapter { |
🎶检查jwsHeader.getKeyId()
只是查找密钥的最常用方法,也可以检查任意数量的标头字段或声明,以确定如何查找验证密钥。
¶Claims 断言
如果正在解析的JWS
具有特定的子sub
值,可以使用JwtParser
上的各种require *
方法来获取数据
1 | try { |
如果缺少某个值,那么就不会捕获InvalidClaimException
,而是捕获MissingClaimException
或IncorrectClaimException
异常
1 | try { |
当然,也可以使用require(fieldName,requiredFieldValue)
方法来获取自定义字段。
1 | try { |
¶压缩
如果JWT
的Claim
域可以足够大,包含许多key/value
对;或者单个值非常冗长,可以通过压缩来减小创建的JWS
的大小
¶默认压缩
如果要压缩JWT
,可以使用JwtBuilde
r 的compressWith(CompressionAlgorithm)
方法。 例如:
1 | Jwts.builder() |
使用DEFLATE
或GZIP
压缩编解码器,但是在解压缩时,不必执行任何操作,不需要配置JwtParser
,JJWT
将按预期自动解压缩主体
¶自定义压缩
如果在创建JWT
时使用自己的自定义压缩编解码器(通过JwtBuilder.compressWith
),则需要使用setCompressionCodecResolver
方法将编解码器提供给JwtParser
。 例如:
1 | CompressionCodecResolver ccr = new MyCompressionCodecResolver(); |
通常,CompressionCodecResolver
实现将检查zip
标头以找出使用的算法,然后返回支持该算法的编解码器实例。 例如:
1 | public class MyCompressionCodecResolver implements CompressionCodecResolver { |