nimbus-jose-jwt 概念
nimbus-jose-jwt是指一个受欢迎的JWT开源库,基于Apache 2.0开源协议,支持所有标准的签名(JWS)和加密(JWE)算法。它提供了一套简单易用的API来处理JWT,适用于对称加密和非对称加密场景。今天整理下这个框架相关的知识点,为授权服务器和资源服务器做铺垫。
🔐 什么是 JWK?
JWK(JSON Web Key) :一个 JSON 格式的密钥(如一个 RSA 公钥)
- JWK 是一个 JSON 对象,用来表示一个加密密钥。
- 它可以是公钥或私钥(但通常只包含公钥,出于安全考虑)。
例如,一个 RSA 公钥的 JWK 可能长这样:
json
{
"kty": "RSA",
"n": "0vx7...Aw",
"e": "AQAB",
"kid": "2011-04-29",
"alg": "RS256"
}
- kty: 密钥类型(如 RSA、EC)
- n, e: RSA 的模数和指数(公钥参数)
- kid: 密钥 ID,用于标识某个特定密钥
- alg: 推荐使用的算法(如 RS256)
📦 什么是 JWKSet?
JWKSet 就是一个包含多个 JWK 的集合
根据标准(RFC 7517),JWKSet 是一个 JSON 对象,其核心是 "keys" 字段,值是一个 JWK 数组。 下面这段话摘自
A JWK Set is a JSON object that represents a set of JWKs. The JSON object MUST have a "keys" member, with its value being an array of JWKs. This JSON object MAY contain whitespace and/or line breaks.
结构如下:
json
{
"keys": [
{ /* 第一个 JWK */ },
{ /* 第二个 JWK */ },
...
],
"custom-member": "自定义字段也允许"
}
🌐 实际应用场景
最常见的使用场景是 OAuth 2.0 / OpenID Connect 协议中的 JWKS(JSON Web Key Set)端点。
比如:
https://accounts.google.com/.well-known/jwks.json
访问这个 URL,你会得到 Google 发布的所有当前有效的公钥(JWKSet),像这样:
json
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "abc123",
"n": "0vx7...",
"e": "AQAB"
},
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "def456",
"n": "1a2b...",
"e": "AQAB"
}
]
}
🧩 用途举例:验证 JWT
- 你收到一个 JWT,header 中有
"kid": "abc123"
。 - 你去 JWKS 端点获取所有公钥。
- 找到
kid
为abc123
的 JWK。 - 使用该 JWK 中的公钥验证 JWT 的签名是否有效。
🧩 一、signedJWT
是什么?
ini
public static void testSignedJwt() {
String token = "eyJraWQiOiIzMzc3MWZiNS1iODdjLTQ3Y2QtYWI3ZS0zOGI4YmQ2OGE2NGEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoib2lkYy1jbGllbnQiLCJuYmYiOjE3NTYzNTY4NjQsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE3NTYzNTcxNjQsImlhdCI6MTc1NjM1Njg2NCwianRpIjoiMzBhNmZmMjctZTUzZi00ZTY3LWFmYjItNDgyMzU1MDAzOTFiIn0.YAX4kyf9R-2NWscRAXWD3gzV_ptkOrB3_-V63K9GnN1O_UEQCKeNk5o6bFGTyRYMjhJdFgDFnpAiHsa1Qe4uB0f3wMIB2Tx7Ob13tZPL9asZCgk9jwIbZojVI9jEBJkTVPHrjTvIhQ0cogQLxB10Pbw2PgPGZ-wVriP647STDi6QRGnHVoMB1u2JrQ16EEBz-8AtapnlOhqmriDh78ar1_na_SHaGj1f4t4IC-GZsbFE-sl3kJJW88aoOJE3OrI3-05-s7lcnHZ0CJE7j8Te7vIcwV4i1sID0hRaYdiWccfIcXUQZPUYrj30h5sXU0UT9lr4PMS_xWZuLID2d6_y0w";
try {
// 这一步只是解析 JWT 字符串,把它变成 Java 对象。 它不会验证签名是否有效!
// 你现在可以安全地读取它的内容,但要相信它是"合法"的,必须经过后续的验证流程。
SignedJWT signedJWT = SignedJWT.parse(token);
System.out.println(signedJWT.getPayload().toJSONObject());
JWSHeader header = signedJWT.getHeader();
JWSAlgorithm algorithm = header.getAlgorithm();
System.out.println(algorithm.getName());
String keyID = header.getKeyID();
System.out.println(keyID);
JWTClaimsSet jwtClaimsSet = signedJWT.getJWTClaimsSet();
System.out.println(jwtClaimsSet.getIssuer());
System.out.println(jwtClaimsSet.getSubject());
System.out.println(jwtClaimsSet.getAudience());
System.out.println(jwtClaimsSet.getIssueTime());
System.out.println(jwtClaimsSet.getExpirationTime());
System.out.println(jwtClaimsSet.getNotBeforeTime());
System.out.println(jwtClaimsSet.getListClaim("scope"));
Base64URL signature = signedJWT.getSignature();
System.out.println(signature.toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
调用SignedJWT.parse(token)
Nimbus 库会:
- 按
.
拆分字符串 - 解码 Header 和 Payload 的 Base64URL
- 把它们转换成结构化的 Java 对象
- 返回一个
SignedJWT
实例
🔍 JWTParser
是什么?
JWTParser
是 Nimbus JOSE + JWT 库中的一个工具类(utility class) ,它的作用是:
根据传入的 JWT 字符串,自动判断它是哪种类型的 JWT(如:Signed、Encrypted、Plaintext),并返回对应的解析结果。
🧩 它能解析哪几种 JWT?
JWT 标准支持三种类型:
类型 | 说明 | 示例格式 |
---|---|---|
1. Plain JWT | 未签名、未加密的 JWT(明文) | header.payload. |
2. Signed JWT | 签名的 JWT(最常见) | header.payload.signature |
3. Encrypted JWT (JWE) | 加密的 JWT | protectedHeader.encryptedKey.iv.ciphertext.authenticationTag |
而 JWTParser.parse(token)
的强大之处在于:
👉 你不需要事先知道这个 token 是哪种类型,它会自动识别!
ini
public static void testJwtParser() {
String token = "eyJraWQiOiIzMzc3MWZiNS1iODdjLTQ3Y2QtYWI3ZS0zOGI4YmQ2OGE2NGEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoib2lkYy1jbGllbnQiLCJuYmYiOjE3NTYzNTY4NjQsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE3NTYzNTcxNjQsImlhdCI6MTc1NjM1Njg2NCwianRpIjoiMzBhNmZmMjctZTUzZi00ZTY3LWFmYjItNDgyMzU1MDAzOTFiIn0.YAX4kyf9R-2NWscRAXWD3gzV_ptkOrB3_-V63K9GnN1O_UEQCKeNk5o6bFGTyRYMjhJdFgDFnpAiHsa1Qe4uB0f3wMIB2Tx7Ob13tZPL9asZCgk9jwIbZojVI9jEBJkTVPHrjTvIhQ0cogQLxB10Pbw2PgPGZ-wVriP647STDi6QRGnHVoMB1u2JrQ16EEBz-8AtapnlOhqmriDh78ar1_na_SHaGj1f4t4IC-GZsbFE-sl3kJJW88aoOJE3OrI3-05-s7lcnHZ0CJE7j8Te7vIcwV4i1sID0hRaYdiWccfIcXUQZPUYrj30h5sXU0UT9lr4PMS_xWZuLID2d6_y0w";
try {
// 这一步只是解析 JWT 字符串,把它变成 Java 对象。 它不会验证签名是否有效!
// 你现在可以安全地读取它的内容,但要相信它是"合法"的,必须经过后续的验证流程。
JWT jwt = JWTParser.parse(token);
if (jwt instanceof SignedJWT) {
System.out.println("this is a signed token");
}
JWSHeader header = (JWSHeader) jwt.getHeader();
JWSAlgorithm algorithm = header.getAlgorithm();
System.out.println(algorithm.getName());
String keyID = header.getKeyID();
System.out.println(keyID);
JWTClaimsSet jwtClaimsSet = jwt.getJWTClaimsSet();
System.out.println(jwtClaimsSet.getIssuer());
System.out.println(jwtClaimsSet.getSubject());
System.out.println(jwtClaimsSet.getAudience());
System.out.println(jwtClaimsSet.getIssueTime());
System.out.println(jwtClaimsSet.getExpirationTime());
System.out.println(jwtClaimsSet.getNotBeforeTime());
System.out.println(jwtClaimsSet.getListClaim("scope"));
String originalString= jwt.getParsedString();
System.out.println(originalString);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
🧠 jwtProcessor
到底是什么?
它的类型是:
DefaultJWTProcessor<SecurityContext>
你可以把它理解为:
🔍 一个"全自动 JWT 验证机器人"
给它一个 JWT,它会完成:找密钥 → 验签名 → 校验时间 → 返回数据
🔍 三、process()
方法到底做了什么?
当你调用:
java
jwtProcessor.process(parsedJwt, null)
它会执行以下几步:
✅ 步骤 1:解析 JWT 的 header
从 parsedJwt.getHeader()
中读取:
"alg"
:签名算法(如RS256
)"kid"
:密钥 ID(如"abc123"
)
✅ 步骤 2:使用 JWSKeySelector
查找匹配的公钥
- 根据
alg
和kid
,去JWKSource
(比如远程 JWKS)中查找对应的 JWK - 把 JWK 转成
RSAPublicKey
或其他类型的公钥
🔁 如果找不到
kid
对应的密钥,会抛异常
✅ 步骤 3:验证签名
- 使用找到的公钥 + 指定算法(如 RS256)
- 对 JWT 的
header.payload
部分重新计算签名 - 和原始 JWT 的
signature
部分对比
❌ 如果不一致 → 签名无效 → 抛
BadJWSException
✅ 步骤 4:验证标准时间字段
自动检查:
exp
(过期时间):是否已过期?nbf
(生效时间):是否还没到生效时间?iat
(签发时间):是否合理?
❌ 如果
exp
已过 → 抛ExpiredJWTException
✅ 步骤 5:返回可信的 JWTClaimsSet
如果以上全部通过,说明这个 JWT 是:
- ✅ 签名合法(没被篡改)
- ✅ 时间有效(没过期)
于是返回一个 可信的 JWTClaimsSet
,里面包含了 payload 中的所有用户信息(如 sub
, name
, email
等)。
🧩 什么是 JWKSource
?("图书馆")
在校验jwt的时候,一定要用到对应的公钥(JWK)才可以进行校验,那么公钥在哪里呢?
JWKSource定义
java
public interface JWKSource<C extends SecurityContext> {
List<JWK> get(JWKSelector selector, C context) throws KeySourceException;
}
它是一个接口,代表一个"可以获取 JWK 的地方"。
✅ JWKSource它能做什么?
- 提供一组 JWK(公钥)
- 支持从不同地方加载:远程 URL、本地文件、内存、数据库等
✅ 常见实现类
实现类 | 来源 | 说明 |
---|---|---|
RemoteJWKSet |
Nimbus 库 | 从 https://.../jwks.json 远程加载 |
ImmutableJWKSet |
Nimbus 库 | 从本地 JSON 字符串或对象加载,只读 |
自定义实现 | 你自己写 | 从数据库、缓存、配置中心加载 |
当调用get
从JWKSource中获取一组JWK的时候 需要提供给一个参数叫JWKSelector
🔍 什么是 JWKSelector
?("检索器")
JWKSelector
是一个"查询条件对象",用来告诉 JWKSource
:
"我要找什么样的公钥?"
ini
public static void testJwkSource() throws MalformedURLException {
String jwkSetUrl = "http://localhost:9000/oauth2/jwks";
URL url = new URL(jwkSetUrl);
JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(url);
JWKMatcher jwkMatcher = new JWKMatcher.Builder()
.keyID("33771fb5-b87c-47cd-ab7e-38b8bd68a64a")
.build();
JWKSelector jwkSelector = new JWKSelector(jwkMatcher);
try {
List<JWK> jwks = jwkSource.get(jwkSelector, null);
for (JWK jwk : jwks) {
System.out.println(jwk.toJSONObject());
}
} catch (KeySourceException e) {
throw new RuntimeException(e);
}
}
jwkselector提供一个select方法用来选择匹配的jwk。
从下面的源码可以看出select方法参数是JWKSet,意思是给我一组jwk,我通过matcher去匹配,把所有符合条件的jwk返回 matcher是JWKMatcher类型,我们在构建JWKSelector的时候作为参数传递到构造器中,也就是说selector底层进行选择的时候需要依赖matcher。matcher定义了匹配机制
ini
JWKMatcher jwkMatcher = new JWKMatcher.Builder()
.keyID("33771fb5-b87c-47cd-ab7e-38b8bd68a64a")
.build();
JWKSelector jwkSelector = new JWKSelector(jwkMatcher);
vbnet
/**
* Selects the keys from the specified JWK set according to the
* matcher's criteria.
*
* @param jwkSet The JWK set. May be {@code null}.
*
* @return The selected keys, ordered by their position in the JWK set,
* empty list if none were matched or the JWK is {@code null}.
*/
public List<JWK> select(final JWKSet jwkSet) {
List<JWK> selectedKeys = new LinkedList<>();
if (jwkSet == null)
return selectedKeys;
for (JWK key: jwkSet.getKeys()) {
if (matcher.matches(key)) {
selectedKeys.add(key);
}
}
return selectedKeys;
}
🧠 jwtProcessor
的执行流程
上面我们提到过jwtProcessor的功能是: 给它一个 JWT,它会完成:找密钥 → 验签名 → 校验时间 → 返回数据
- 因为它要寻找秘钥(JWKSET),所以需要定义一个JWKSource
- 验签的时候还需要匹配符合条件的(JWK),那就需要JWKSelector,JWSVerificationKeySelector是一个专门用来进行签名校验时的key选择器,底层包装了JWKSelector。
- JWTClaimsSetVerifier是用来进行验证claims各项声明是否符合要求,比如时间过期啊,像下面这种就是将JWTClaimsSetVerifier置空,也就是不进行校验。springsecurity就采用了这种方式,因为springsecurity单独进行的校验,脱离了JOSE-JWT框架。
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
ini
public static void testProcessor() throws MalformedURLException {
String jwkSetUrl = "http://localhost:9000/oauth2/jwks";
URL url = new URL(jwkSetUrl);
JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(url);
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
// Creates a new JWS verification key selector. 见名之意 专门用来进行签名验证的key选择器,底层会包装一个JWKSelector
JWSVerificationKeySelector<SecurityContext> jwsVerificationKeySelector = new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
jwtProcessor.setJWSKeySelector(jwsVerificationKeySelector);
// Spring Security validates the claim set independent from Nimbus
// 覆盖了默认的校验逻辑,这样就不会校验claims中的字段是否符合 比如是否过期
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
});
// Verify the signature
String token = "eyJraWQiOiIzMzc3MWZiNS1iODdjLTQ3Y2QtYWI3ZS0zOGI4YmQ2OGE2NGEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoib2lkYy1jbGllbnQiLCJuYmYiOjE3NTYzNTY4NjQsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDAiLCJleHAiOjE3NTYzNTcxNjQsImlhdCI6MTc1NjM1Njg2NCwianRpIjoiMzBhNmZmMjctZTUzZi00ZTY3LWFmYjItNDgyMzU1MDAzOTFiIn0.YAX4kyf9R-2NWscRAXWD3gzV_ptkOrB3_-V63K9GnN1O_UEQCKeNk5o6bFGTyRYMjhJdFgDFnpAiHsa1Qe4uB0f3wMIB2Tx7Ob13tZPL9asZCgk9jwIbZojVI9jEBJkTVPHrjTvIhQ0cogQLxB10Pbw2PgPGZ-wVriP647STDi6QRGnHVoMB1u2JrQ16EEBz-8AtapnlOhqmriDh78ar1_na_SHaGj1f4t4IC-GZsbFE-sl3kJJW88aoOJE3OrI3-05-s7lcnHZ0CJE7j8Te7vIcwV4i1sID0hRaYdiWccfIcXUQZPUYrj30h5sXU0UT9lr4PMS_xWZuLID2d6_y0w";
SignedJWT parsedJwt = null;
try {
parsedJwt = SignedJWT.parse(token);
} catch (ParseException e) {
throw new RuntimeException(e);
}
try {
JWTClaimsSet jwtClaimsSet = jwtProcessor.process(parsedJwt, null);
System.out.println(jwtClaimsSet.getIssuer());
System.out.println(jwtClaimsSet.getSubject());
System.out.println(jwtClaimsSet.getAudience());
System.out.println(jwtClaimsSet.getIssueTime());
} catch (BadJOSEException | JOSEException e) {
throw new RuntimeException(e);
}
}
总结
本文主要分享了nimbus-jose-jwt中一些常用类的用法和概念
- 什么是JWK
- 什么是JWKSET
- 什么是JWKSource
- 什么是JWKMatcher
- 什么是JWKSelector
- 什么是JWTProcessor
最后奉上一张图 希望大家了解jwt
