Spring Oauth2-Authorization-Server OAuth2TokenGenerator token生成
基于 spring-security-oauth2-authorization-server 0.2.3
OAuth2TokenGenerator
@FunctionalInterface
public interface OAuth2TokenGenerator<T extends OAuth2Token> {
/**
* Generate an OAuth 2.0 Token using the attributes contained in the {@link OAuth2TokenContext},
* or return {@code null} if the {@link OAuth2TokenContext#getTokenType()} is not supported.
*
* <p>
* If the returned {@link OAuth2Token} has a set of claims, it should implement {@link ClaimAccessor}
* in order for it to be stored with the {@link OAuth2Authorization}.
*
* @param context the context containing the OAuth 2.0 Token attributes
* @return an {@link OAuth2Token} or {@code null} if the {@link OAuth2TokenContext#getTokenType()} is not supported
*/
@Nullable
T generate(OAuth2TokenContext context);
}
目前有几种实现:
- OAuth2RefreshTokenGenerator
- OAuth2AccessTokenGenerator: 当 OAuth2TokenFormat.REFERENCE “reference” 时 生成token 规则
- JwtGenerator: 当 OAuth2TokenFormat 为 OAuth2TokenFormat.SELF_CONTAINED
服务器端生成的token 格式有:
- OAuth2TokenFormat.REFERENCE
- OAuth2TokenFormat.SELF_CONTAINED: 加密,如JWT, 默认,需要提供JWK
JwtGenerator
生成的token 为Jwt 方式
public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
/**
* Constructs a {@code JwtGenerator} using the provided parameters.
*
* @param jwtEncoder the jwt encoder
*/
public JwtGenerator(JwtEncoder jwtEncoder) {
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
this.jwtEncoder = jwtEncoder;
}
@Nullable
@Override
public Jwt generate(OAuth2TokenContext context) {
if (context.getTokenType() == null ||
(!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) &&
!OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue()))) {
return null;
}
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) &&
!OAuth2TokenFormat.SELF_CONTAINED
.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
return null;
}
//...omit
Jwt jwt = this.jwtEncoder.encode(headers, claims);
return jwt;
}
}
这里的 JwtEncoder 默认使用 SignatureAlgorithm.RS256
默认的tokenSetting:
public static Builder builder() {
return new Builder()
.accessTokenTimeToLive(Duration.ofMinutes(5))
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
.reuseRefreshTokens(true)
.refreshTokenTimeToLive(Duration.ofMinutes(60))
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);
}
如果要生成 Jwt, 就需要提供JWK 获取方式,如:
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
如上配置, 在 oauth2 客户端就可以通过 默认的 /oauth2/jwks
获取解密密钥, 在 NimbusJwkSetEndpointFilter
中
public final class NimbusJwkSetEndpointFilter extends OncePerRequestFilter {
/**
* The default endpoint {@code URI} for JWK Set requests.
*/
private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks";
private final JWKSource<SecurityContext> jwkSource;
private final JWKSelector jwkSelector;
private final RequestMatcher requestMatcher;
/**
* Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters.
* @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
*/
public NimbusJwkSetEndpointFilter(JWKSource<SecurityContext> jwkSource) {
this(jwkSource, DEFAULT_JWK_SET_ENDPOINT_URI);
}
/**
* Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters.
*
* @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
* @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests
*/
public NimbusJwkSetEndpointFilter(JWKSource<SecurityContext> jwkSource, String jwkSetEndpointUri) {
Assert.notNull(jwkSource, "jwkSource cannot be null");
Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty");
this.jwkSource = jwkSource;
this.jwkSelector = new JWKSelector(new JWKMatcher.Builder().build());
this.requestMatcher = new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name());
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.requestMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
JWKSet jwkSet;
try {
jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null));
}
catch (Exception ex) {
throw new IllegalStateException("Failed to select the JWK(s) -> " + ex.getMessage(), ex);
}
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
try (Writer writer = response.getWriter()) {
writer.write(jwkSet.toString()); // toString() excludes private keys
}
}
}
OAuth2AccessTokenGenerator
默认是Base64 生成的token:
public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
private final StringKeyGenerator accessTokenGenerator =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
@Nullable
@Override
public OAuth2AccessToken generate(OAuth2TokenContext context) {
// 必须是 OAuth2TokenFormat.REFERENCE 的token 格式
if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) ||
!OAuth2TokenFormat.REFERENCE
.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
return null;
}
//omit...
OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
OAuth2AccessToken accessToken = new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER,
this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(),
accessTokenClaimsSet.getExpiresAt(),
context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims());
return accessToken;
}
}