Skip to content

Commit b592204

Browse files
committed
Add id-token-jwk-set-url ClientSettings and claim mapping
- Add ID_TOKEN_JWK_SET_URL to ConfigurationSettingNames and typed getter/builder to ClientSettings - Store subject token claims as authorization attribute so OAuth2TokenCustomizer can map ID token claims to the generated access token - Update OidcIdTokenSubjectTokenResolver JavaDoc with ClientSettings configuration example Closes gh-19048 Signed-off-by: Bapuji Koraganti <bapuk.2008@gmail.com>
1 parent 3c297de commit b592204

6 files changed

Lines changed: 79 additions & 0 deletions

File tree

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
8080

8181
private static final String MAY_ACT = "may_act";
8282

83+
/**
84+
* The attribute name for the subject token claims stored in the
85+
* {@link OAuth2Authorization}. These claims are available to
86+
* {@link org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer}
87+
* implementations for mapping external ID token claims to the generated access token.
88+
* @since 7.0
89+
*/
90+
public static final String SUBJECT_TOKEN_CLAIMS_ATTRIBUTE = OAuth2TokenExchangeSubjectTokenContext.class.getName()
91+
+ ".CLAIMS";
92+
8393
private final Log logger = LogFactory.getLog(getClass());
8494

8595
private final OAuth2AuthorizationService authorizationService;
@@ -155,6 +165,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
155165
.principalName(subjectTokenContext.getPrincipalName())
156166
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
157167
.attribute(Principal.class.getName(), subjectTokenContext.getPrincipal())
168+
.attribute(SUBJECT_TOKEN_CLAIMS_ATTRIBUTE, subjectTokenContext.getClaims())
158169
.build();
159170
// @formatter:on
160171

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcIdTokenSubjectTokenResolver.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,27 @@
4444
* token using a {@link JwtDecoder} obtained from the provided factory, then constructs an
4545
* {@link OAuth2TokenExchangeSubjectTokenContext} from the token's claims.
4646
*
47+
* <p>
48+
* The ID token's JWKS endpoint can be configured per client using
49+
* {@link org.springframework.security.oauth2.server.authorization.settings.ClientSettings#getIdTokenJwkSetUrl()}.
50+
* Example configuration:
51+
*
52+
* <pre>
53+
* &#064;Bean
54+
* OidcIdTokenSubjectTokenResolver subjectTokenResolver() {
55+
* return new OidcIdTokenSubjectTokenResolver((registeredClient) -&gt; {
56+
* String jwkSetUrl = registeredClient.getClientSettings().getIdTokenJwkSetUrl();
57+
* return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).build();
58+
* });
59+
* }
60+
* </pre>
61+
*
4762
* @author Bapuji Koraganti
4863
* @since 7.0
4964
* @see OAuth2TokenExchangeSubjectTokenResolver
5065
* @see OAuth2TokenExchangeSubjectTokenContext
5166
* @see JwtDecoderFactory
67+
* @see org.springframework.security.oauth2.server.authorization.settings.ClientSettings#getIdTokenJwkSetUrl()
5268
*/
5369
public final class OidcIdTokenSubjectTokenResolver implements OAuth2TokenExchangeSubjectTokenResolver {
5470

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ public boolean isRequireAuthorizationConsent() {
9999
return getSetting(ConfigurationSettingNames.Client.X509_CERTIFICATE_SUBJECT_DN);
100100
}
101101

102+
/**
103+
* Returns the {@code URL} for the external Identity Provider's JSON Web Key Set used
104+
* to validate ID tokens during token exchange.
105+
* @return the {@code URL} for the external IdP's JSON Web Key Set, or {@code null} if
106+
* not set
107+
* @since 7.0
108+
*/
109+
public @Nullable String getIdTokenJwkSetUrl() {
110+
return getSetting(ConfigurationSettingNames.Client.ID_TOKEN_JWK_SET_URL);
111+
}
112+
102113
/**
103114
* Constructs a new {@link Builder} with the default settings.
104115
* @return the {@link Builder}
@@ -185,6 +196,17 @@ public Builder x509CertificateSubjectDN(String x509CertificateSubjectDN) {
185196
return setting(ConfigurationSettingNames.Client.X509_CERTIFICATE_SUBJECT_DN, x509CertificateSubjectDN);
186197
}
187198

199+
/**
200+
* Sets the {@code URL} for the external Identity Provider's JSON Web Key Set used
201+
* to validate ID tokens during token exchange.
202+
* @param idTokenJwkSetUrl the {@code URL} for the external IdP's JSON Web Key Set
203+
* @return the {@link Builder} for further configuration
204+
* @since 7.0
205+
*/
206+
public Builder idTokenJwkSetUrl(String idTokenJwkSetUrl) {
207+
return setting(ConfigurationSettingNames.Client.ID_TOKEN_JWK_SET_URL, idTokenJwkSetUrl);
208+
}
209+
188210
/**
189211
* Builds the {@link ClientSettings}.
190212
* @return the {@link ClientSettings}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ public static final class Client {
7878
public static final String X509_CERTIFICATE_SUBJECT_DN = CLIENT_SETTINGS_NAMESPACE
7979
.concat("x509-certificate-subject-dn");
8080

81+
/**
82+
* Set the {@code URL} for the external Identity Provider's JSON Web Key Set used
83+
* to validate ID tokens during token exchange.
84+
* @since 7.0
85+
*/
86+
public static final String ID_TOKEN_JWK_SET_URL = CLIENT_SETTINGS_NAMESPACE.concat("id-token-jwk-set-url");
87+
8188
private Client() {
8289
}
8390

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,15 @@ public void authenticateWhenSubjectTokenResolverReturnsContextThenReturnAccessTo
698698
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
699699
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isSameAs(userPrincipal);
700700
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken);
701+
702+
// Verify subject token claims are available via the token context authorization
703+
OAuth2Authorization subjectAuth = tokenContext.getAuthorization();
704+
assertThat(subjectAuth).isNotNull();
705+
Map<String, Object> subjectTokenClaims = subjectAuth
706+
.getAttribute(OAuth2TokenExchangeAuthenticationProvider.SUBJECT_TOKEN_CLAIMS_ATTRIBUTE);
707+
assertThat(subjectTokenClaims).isNotNull();
708+
assertThat(subjectTokenClaims).containsEntry("iss", "https://gitlab.com");
709+
assertThat(subjectTokenClaims).containsEntry("sub", "user@example.com");
701710
}
702711

703712
@Test

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettingsTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ public void x509CertificateSubjectDNWhenProvidedThenSet() {
7272
.isEqualTo("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US");
7373
}
7474

75+
@Test
76+
public void idTokenJwkSetUrlWhenProvidedThenSet() {
77+
ClientSettings clientSettings = ClientSettings.builder()
78+
.idTokenJwkSetUrl("https://gitlab.com/oauth/discovery/keys")
79+
.build();
80+
assertThat(clientSettings.getIdTokenJwkSetUrl()).isEqualTo("https://gitlab.com/oauth/discovery/keys");
81+
}
82+
83+
@Test
84+
public void idTokenJwkSetUrlWhenNotSetThenNull() {
85+
ClientSettings clientSettings = ClientSettings.builder().build();
86+
assertThat(clientSettings.getIdTokenJwkSetUrl()).isNull();
87+
}
88+
7589
@Test
7690
public void settingWhenCustomThenSet() {
7791
ClientSettings clientSettings = ClientSettings.builder()

0 commit comments

Comments
 (0)