在oauth2场景中 授权服务器在完成授权请求后,会给客户端签发accessToken。那么这个过程是如何的呢?
一.顶级抽象 JwtEncoder
java
package org.springframework.security.oauth2.jwt;
@FunctionalInterface
public interface JwtEncoder {
/**
* Encode the JWT to it's compact claims representation format.
* @param parameters the parameters containing the JOSE header and JWT Claims Set
* @return a {@link Jwt}
* @throws JwtEncodingException if an error occurs while attempting to encode the JWT
*/
Jwt encode(JwtEncoderParameters parameters) throws JwtEncodingException;
}
springsecurity为oauth2提供了一个规范JwtEncoder,是一个函数式接口,正如接口文档描述的那样,接口的作用就是把JWT编码成它的紧凑格式
JWT 的紧凑格式 长这样:
text
xxxx.yyyy.zzzz
可能有人会疑问 从返回值上看返回的是JWT对象,不是一个紧凑格式的字符串。但是我们看一下JWT的继承关系:

JWT继承于Oauth2Token,Oauth2Token
也是springsecurity提供的接口规范,说明JWT可以通过getTokenValue
方法返回字符串格式的token。
typescript
package org.springframework.security.oauth2.core;
public interface OAuth2Token {
/**
* Returns the token value.
* @return the token value
*/
String getTokenValue();
/**
* Returns the time at which the token was issued.
* @return the time the token was issued or {@code null}
*/
@Nullable
default Instant getIssuedAt() {
return null;
}
/**
* Returns the expiration time on or after which the token MUST NOT be accepted.
* @return the token expiration time or {@code null}
*/
@Nullable
default Instant getExpiresAt() {
return null;
}
}
二.JwtEncoder的具体实现NimbusJwtEncoder
SpringSecurity也提供了默认的实现NimbusJwtEncoder
,通过名称可以看出底层是对开源nimbus-jose-jwt
的包装。我们姑且不去深入研究底层的实现,我们把目光停留在框架层面。
下面是NimbusJwtEncoder中的具体实现,我们发现如果要想进行jwt的生成,在框架层面必须先提供JwtEncoderParameters。
ini
@Override
public Jwt encode(JwtEncoderParameters parameters) throws JwtEncodingException {
Assert.notNull(parameters, "parameters cannot be null");
JwsHeader headers = parameters.getJwsHeader();
if (headers == null) {
headers = DEFAULT_JWS_HEADER;
}
JwtClaimsSet claims = parameters.getClaims();
JWK jwk = selectJwk(headers);
headers = addKeyIdentifierHeadersIfNecessary(headers, jwk);
String jws = serialize(headers, claims, jwk);
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(), headers.getHeaders(), claims.getClaims());
}
JwtEncoderParameters
也是springsecurity为oauth2设计的一个类,里面包装了JwsHeader和 JwtClaimsSet
kotlin
package org.springframework.security.oauth2.jwt;
public final class JwtEncoderParameters {
private final JwsHeader jwsHeader;
private final JwtClaimsSet claims;
private JwtEncoderParameters(JwsHeader jwsHeader, JwtClaimsSet claims) {
this.jwsHeader = jwsHeader;
this.claims = claims;
}
}
因为jwt的三个部分分别是header.claims.signature
,所以用 JwtEncoderParameters封装了前两个部分,供JwtEncoder使用。
三.JwtClaimsSet 的构建
JWT Claims Set:JWT 的载荷(payload),比如:
json
{ "sub": "123", "name": "张三", "exp": 1735689600 }
我们使用JwtClaimsSet来封装JWT的payload,例如如下代码: 通过构建者模式build了一个JwtClaimsSet对象。
ini
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
claimsBuilder
.issuer("http://localhost:9000")
.subject("admin")
// 全称是 Audience,中文意思是"受众"或"目标接收者"
// 告诉接收方 ------ 这个 token 是不是发给你的
.audience(Collections.singletonList("client01"))
// 这个 JWT 是什么时候签发的。 "iat" = "Issued At"
.issuedAt(issuedAt)
// 这个 JWT 什么时候过期。 "exp" = "Expiration Time"
.expiresAt(expiresAt)
.id(UUID.randomUUID().toString());
// 这个 JWT 在此时间之前不能被接受处理。 在这个时间点之前,任何接收方都应拒绝使用该 token;
claimsBuilder.notBefore(issuedAt);
// 多个scopes
claimsBuilder.claim("scope", List.of("read", "write"));
JwtClaimsSet claims = claimsBuilder.build();
JwtClaimsSet里面就是个map,将所有的payload通过key value的形式存储在map中

四.JwsHeader的构建
JOSE Header:JWT 的头部,比如:
json
{ "alg": "HS256", "typ": "JWT" }
springsecurity使用 JwsHeader来封装JWT的头部 如下:
ini
JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256;
JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm);
JwsHeader jwsHeader = jwsHeaderBuilder.build();
JwsHeader的底层也是有个map属性,用来保存头部中所有指定的键值对,如下图:

五.详细说说NimbusJwtEncoder
1. NimbusJwtEncoder
是什么?
- 是 Spring Security 中用于生成 JWT 的一个具体实现。
- 基于 Nimbus JOSE + JWT 库。
- 负责把
JwtClaimsSet
和JwsHeader
打包并签名成一个 JWT 字符串。
NimbusJwtEncoder要想实现对jwt的签名,必须使用到秘钥信息。在nimbus中 JWK 用来表示秘钥。
我们在创建NimbusJwtEncoder的时候需要提供jwkSet ,JWKSet
就是密钥的集合。
ini
NimbusJwtEncoder nimbusJwtEncoder = new NimbusJwtEncoder(new ImmutableJWKSet<>(jwkSet));
你是在告诉 encoder:
"我有一组密钥(
jwkSet
),你从中选一个合适的来签名 JWT。"
2.使用非对称密钥(RSA)
如果我想用RSA非对称加密方式,那么在创建NimbusJwtEncoder的时候,你需要告诉encoder,在你的jwkSet
集合中有一个是RSA.
我们可以先生成一堆KeyPair
ini
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
然后将其包装成JWK,RSAKey就是Nimbus对jwk的一种实现。也就是说RSAKey继承了JWK
ini
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// package com.nimbusds.jose.jwk;
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID("ilabx-oauth2-20250830")
.build();
System.out.println(rsaKey.toJSONString());
JWKSet jwkSet = new JWKSet(rsaKey);
上面的代码将生成的私钥包装成RSAKey
,最后放到了jwkset集合中,被encoder使用
五.最后一步 生成jwt
ini
JwtEncoderParameters jwtEncoderParameters = JwtEncoderParameters.from(jwsHeader, claims);
NimbusJwtEncoder nimbusJwtEncoder = new NimbusJwtEncoder(new ImmutableJWKSet<>(jwkSet));
// 签发 JWT 格式的 Access Token,但签名需要 私钥(private key)
Jwt jwt = nimbusJwtEncoder.encode(jwtEncoderParameters);
System.out.println(jwt);
System.out.println(jwt.getTokenValue());
六. 完整demo代码如下
java
public class JwtTest {
public static void main(String[] args) {
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
claimsBuilder
.issuer("http://localhost:9000")
.subject("admin")
// 全称是 Audience,中文意思是"受众"或"目标接收者"
// 告诉接收方 ------ 这个 token 是不是发给你的
.audience(Collections.singletonList("client01"))
// 这个 JWT 是什么时候签发的。 "iat" = "Issued At"
.issuedAt(issuedAt)
// 这个 JWT 什么时候过期。 "exp" = "Expiration Time"
.expiresAt(expiresAt)
.id(UUID.randomUUID().toString());
// 这个 JWT 在此时间之前不能被接受处理。 在这个时间点之前,任何接收方都应拒绝使用该 token;
claimsBuilder.notBefore(issuedAt);
// 多个scopes
claimsBuilder.claim("scope", List.of("read", "write"));
JwtClaimsSet claims = claimsBuilder.build();
System.out.println(claims);
System.out.println(claims);
JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256;
JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm);
JwsHeader jwsHeader = jwsHeaderBuilder.build();
JwtEncoderParameters jwtEncoderParameters = JwtEncoderParameters.from(jwsHeader, claims);
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// package com.nimbusds.jose.jwk;
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID("ilabx-oauth2-20250830")
.build();
System.out.println(rsaKey.toJSONString());
JWKSet jwkSet = new JWKSet(rsaKey);
NimbusJwtEncoder nimbusJwtEncoder = new NimbusJwtEncoder(new ImmutableJWKSet<>(jwkSet));
// 签发 JWT 格式的 Access Token,但签名需要 私钥(private key)
Jwt jwt = nimbusJwtEncoder.encode(jwtEncoderParameters);
System.out.println(jwt);
System.out.println(jwt.getTokenValue());
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}