Fix package tangle
Closes gh-1615
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.token;
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
@@ -22,7 +22,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
@@ -32,28 +31,36 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
*/
|
||||
final class DefaultOAuth2TokenClaimsConsumer implements Consumer<Map<String, Object>> {
|
||||
private final OAuth2TokenContext context;
|
||||
final class DefaultOAuth2TokenCustomizers {
|
||||
|
||||
DefaultOAuth2TokenClaimsConsumer(OAuth2TokenContext context) {
|
||||
this.context = context;
|
||||
static OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
||||
return (context) -> context.getClaims().claims((claims) -> customize(context, claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Map<String, Object> claims) {
|
||||
static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
|
||||
return (context) -> context.getClaims().claims((claims) -> customize(context, claims));
|
||||
}
|
||||
|
||||
private static void customize(OAuth2TokenContext tokenContext, Map<String, Object> claims) {
|
||||
// Add 'cnf' claim for Mutual-TLS Client Certificate-Bound Access Tokens
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(this.context.getTokenType()) &&
|
||||
this.context.getAuthorizationGrant() != null &&
|
||||
this.context.getAuthorizationGrant().getPrincipal() instanceof OAuth2ClientAuthenticationToken clientAuthentication) {
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenContext.getTokenType()) &&
|
||||
tokenContext.getAuthorizationGrant() != null &&
|
||||
tokenContext.getAuthorizationGrant().getPrincipal() instanceof OAuth2ClientAuthenticationToken clientAuthentication) {
|
||||
|
||||
if ((ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) ||
|
||||
ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())) &&
|
||||
this.context.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) {
|
||||
tokenContext.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) {
|
||||
|
||||
X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials();
|
||||
try {
|
||||
@@ -71,7 +78,7 @@ final class DefaultOAuth2TokenClaimsConsumer implements Consumer<Map<String, Obj
|
||||
|
||||
// Add 'act' claim for delegation use case of Token Exchange Grant.
|
||||
// If more than one actor is present, we create a chain of delegation by nesting "act" claims.
|
||||
if (this.context.getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
|
||||
if (tokenContext.getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
|
||||
Map<String, Object> currentClaims = claims;
|
||||
for (OAuth2TokenExchangeActor actor : compositeAuthenticationToken.getActors()) {
|
||||
Map<String, Object> actorClaims = actor.getClaims();
|
||||
@@ -105,10 +105,7 @@ final class OAuth2ConfigurerUtils {
|
||||
if (tokenGenerator == null) {
|
||||
JwtGenerator jwtGenerator = getJwtGenerator(httpSecurity);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(httpSecurity);
|
||||
if (accessTokenCustomizer != null) {
|
||||
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
|
||||
}
|
||||
accessTokenGenerator.setAccessTokenCustomizer(getAccessTokenCustomizer(httpSecurity));
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
if (jwtGenerator != null) {
|
||||
tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
@@ -129,10 +126,7 @@ final class OAuth2ConfigurerUtils {
|
||||
JwtEncoder jwtEncoder = getJwtEncoder(httpSecurity);
|
||||
if (jwtEncoder != null) {
|
||||
jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(httpSecurity);
|
||||
if (jwtCustomizer != null) {
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
jwtGenerator.setJwtCustomizer(getJwtCustomizer(httpSecurity));
|
||||
httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator);
|
||||
}
|
||||
}
|
||||
@@ -170,13 +164,29 @@ final class OAuth2ConfigurerUtils {
|
||||
}
|
||||
|
||||
private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
|
||||
final OAuth2TokenCustomizer<JwtEncodingContext> defaultJwtCustomizer = DefaultOAuth2TokenCustomizers.jwtCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
|
||||
return getOptionalBean(httpSecurity, type);
|
||||
final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getOptionalBean(httpSecurity, type);
|
||||
if (jwtCustomizer == null) {
|
||||
return defaultJwtCustomizer;
|
||||
}
|
||||
return (context) -> {
|
||||
defaultJwtCustomizer.customize(context);
|
||||
jwtCustomizer.customize(context);
|
||||
};
|
||||
}
|
||||
|
||||
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {
|
||||
final OAuth2TokenCustomizer<OAuth2TokenClaimsContext> defaultAccessTokenCustomizer = DefaultOAuth2TokenCustomizers.accessTokenCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
|
||||
return getOptionalBean(httpSecurity, type);
|
||||
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getOptionalBean(httpSecurity, type);
|
||||
if (accessTokenCustomizer == null) {
|
||||
return defaultAccessTokenCustomizer;
|
||||
}
|
||||
return (context) -> {
|
||||
defaultAccessTokenCustomizer.customize(context);
|
||||
accessTokenCustomizer.customize(context);
|
||||
};
|
||||
}
|
||||
|
||||
static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -144,7 +144,6 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
|
||||
}
|
||||
}
|
||||
}
|
||||
claimsBuilder.claims(new DefaultOAuth2TokenClaimsConsumer(context));
|
||||
// @formatter:on
|
||||
|
||||
JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -84,7 +84,6 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OA
|
||||
if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
|
||||
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
|
||||
}
|
||||
claimsBuilder.claims(new DefaultOAuth2TokenClaimsConsumer(context));
|
||||
// @formatter:on
|
||||
|
||||
if (this.accessTokenCustomizer != null) {
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.JwsHeader;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultOAuth2TokenCustomizers}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
class DefaultOAuth2TokenCustomizersTests {
|
||||
private static final String ISSUER_1 = "issuer-1";
|
||||
private static final String ISSUER_2 = "issuer-2";
|
||||
private JwsHeader.Builder jwsHeaderBuilder;
|
||||
private JwtClaimsSet.Builder jwtClaimsBuilder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.jwsHeaderBuilder = JwsHeader.with(SignatureAlgorithm.RS256);
|
||||
this.jwtClaimsBuilder = JwtClaimsSet.builder().issuer(ISSUER_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() {
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.REFRESH_TOKEN)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenAuthorizationGrantIsNullThenNoClaimsAdded() {
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getResources()).thenReturn(Set.of("resource1", "resource2"));
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.authorizationGrant(tokenExchangeAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
// We do not populate claims (e.g. `aud`) based on the resource parameter
|
||||
assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Set.of("audience1", "audience2"));
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.authorizationGrant(tokenExchangeAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
// NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter
|
||||
assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenTokenExchangeGrantAndDelegationThenActClaimAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Collections.emptySet());
|
||||
|
||||
Authentication subject = new TestingAuthenticationToken("subject", null);
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(JwtClaimNames.ISS, ISSUER_1,
|
||||
JwtClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(JwtClaimNames.ISS, ISSUER_2,
|
||||
JwtClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
subject, List.of(actor1, actor2));
|
||||
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.principal(principal)
|
||||
.authorizationGrant(tokenExchangeAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
assertThat(jwtClaimsSet.getClaims()).isNotEmpty();
|
||||
assertThat(jwtClaimsSet.getClaims()).hasSize(2);
|
||||
assertThat(jwtClaimsSet.getClaims().get("act")).isNotNull();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> actClaim1 = (Map<String, Object>) jwtClaimsSet.getClaims().get("act");
|
||||
assertThat(actClaim1.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_1);
|
||||
assertThat(actClaim1.get(JwtClaimNames.SUB)).isEqualTo("actor1");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> actClaim2 = (Map<String, Object>) actClaim1.get("act");
|
||||
assertThat(actClaim2.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_2);
|
||||
assertThat(actClaim2.get(JwtClaimNames.SUB)).isEqualTo("actor2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenPKIX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
|
||||
.clientSettings(
|
||||
ClientSettings.builder()
|
||||
.x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName())
|
||||
.build()
|
||||
)
|
||||
.tokenSettings(
|
||||
TokenSettings.builder()
|
||||
.x509CertificateBoundAccessTokens(true)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.registeredClient(registeredClient)
|
||||
.authorizationGrant(clientCredentialsAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
assertThat(jwtClaimsSet.getClaims()).isNotEmpty();
|
||||
assertThat(jwtClaimsSet.getClaims()).hasSize(2);
|
||||
Map<String, Object> cnfClaim = jwtClaimsSet.getClaim("cnf");
|
||||
assertThat(cnfClaim).isNotEmpty();
|
||||
assertThat(cnfClaim.get("x5t#S256")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenSelfSignedX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
|
||||
.clientSettings(
|
||||
ClientSettings.builder()
|
||||
.jwkSetUrl("https://client.example.com/jwks")
|
||||
.build()
|
||||
)
|
||||
.tokenSettings(
|
||||
TokenSettings.builder()
|
||||
.x509CertificateBoundAccessTokens(true)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.registeredClient(registeredClient)
|
||||
.authorizationGrant(clientCredentialsAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext);
|
||||
JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build();
|
||||
assertThat(jwtClaimsSet.getClaims()).isNotEmpty();
|
||||
assertThat(jwtClaimsSet.getClaims()).hasSize(2);
|
||||
Map<String, Object> cnfClaim = jwtClaimsSet.getClaim("cnf");
|
||||
assertThat(cnfClaim).isNotEmpty();
|
||||
assertThat(cnfClaim.get("x5t#S256")).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.token;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultOAuth2TokenClaimsConsumer}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class DefaultOAuth2TokenClaimsConsumerTests {
|
||||
|
||||
private OAuth2TokenContext tokenContext;
|
||||
|
||||
private Consumer<Map<String, Object>> consumer;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.tokenContext = mock(OAuth2TokenContext.class);
|
||||
this.consumer = new DefaultOAuth2TokenClaimsConsumer(this.tokenContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() {
|
||||
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.REFRESH_TOKEN);
|
||||
Map<String, Object> claims = new LinkedHashMap<>();
|
||||
this.consumer.accept(claims);
|
||||
assertThat(claims).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenAuthorizationGrantIsNullThenNoClaimsAdded() {
|
||||
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
|
||||
when(this.tokenContext.getAuthorizationGrant()).thenReturn(null);
|
||||
Map<String, Object> claims = new LinkedHashMap<>();
|
||||
this.consumer.accept(claims);
|
||||
assertThat(claims).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getResources()).thenReturn(Set.of("resource1", "resource2"));
|
||||
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
|
||||
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
|
||||
Map<String, Object> claims = new LinkedHashMap<>();
|
||||
this.consumer.accept(claims);
|
||||
// We do not populate claims (e.g. `aud`) based on the resource parameter
|
||||
assertThat(claims).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Set.of("audience1", "audience2"));
|
||||
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
|
||||
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
|
||||
Map<String, Object> claims = new LinkedHashMap<>();
|
||||
this.consumer.accept(claims);
|
||||
// NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter
|
||||
assertThat(claims).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenTokenExchangeGrantAndDelegationThenActClaimAdded() {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
|
||||
OAuth2TokenExchangeAuthenticationToken.class);
|
||||
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Collections.emptySet());
|
||||
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
|
||||
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
|
||||
Authentication subject = new TestingAuthenticationToken("subject", null);
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
|
||||
OAuth2TokenClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2",
|
||||
OAuth2TokenClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
subject, List.of(actor1, actor2));
|
||||
when(this.tokenContext.getPrincipal()).thenReturn(principal);
|
||||
Map<String, Object> claims = new LinkedHashMap<>();
|
||||
this.consumer.accept(claims);
|
||||
assertThat(claims).hasSize(1);
|
||||
assertThat(claims.get("act")).isNotNull();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> actClaim1 = (Map<String, Object>) claims.get("act");
|
||||
assertThat(actClaim1.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer1");
|
||||
assertThat(actClaim1.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor1");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> actClaim2 = (Map<String, Object>) actClaim1.get("act");
|
||||
assertThat(actClaim2.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer2");
|
||||
assertThat(actClaim2.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor2");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,10 +53,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@@ -130,26 +128,11 @@ public class JwtGeneratorTests {
|
||||
|
||||
@Test
|
||||
public void generateWhenAccessTokenTypeThenReturnJwt() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
|
||||
.clientSettings(
|
||||
ClientSettings.builder()
|
||||
.x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName())
|
||||
.build()
|
||||
)
|
||||
.tokenSettings(
|
||||
TokenSettings.builder()
|
||||
.x509CertificateBoundAccessTokens(true)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
@@ -342,17 +325,6 @@ public class JwtGeneratorTests {
|
||||
|
||||
Set<String> scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE);
|
||||
assertThat(scopes).isEqualTo(tokenContext.getAuthorizedScopes());
|
||||
|
||||
OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) tokenContext.getAuthorizationGrant().getPrincipal();
|
||||
if (ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) &&
|
||||
tokenContext.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) {
|
||||
Map<String, Object> cnf = jwtClaimsSet.getClaim("cnf");
|
||||
assertThat(cnf).isNotEmpty();
|
||||
assertThat(cnf.get("x5t#S256")).isNotNull();
|
||||
} else {
|
||||
Map<String, Object> cnf = jwtClaimsSet.getClaim("cnf");
|
||||
assertThat(cnf).isEmpty();
|
||||
}
|
||||
} else {
|
||||
assertThat(jwtClaimsSet.<String>getClaim(IdTokenClaimNames.AZP)).isEqualTo(tokenContext.getRegisteredClient().getClientId());
|
||||
if (tokenContext.getAuthorizationGrantType().equals(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,6 @@ package org.springframework.security.oauth2.server.authorization.token;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -42,10 +41,8 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@@ -117,16 +114,10 @@ public class OAuth2AccessTokenGeneratorTests {
|
||||
@Test
|
||||
public void generateWhenReferenceAccessTokenTypeThenReturnAccessToken() {
|
||||
// @formatter:off
|
||||
ClientSettings clientSettings = ClientSettings.builder()
|
||||
.x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName())
|
||||
.build();
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.x509CertificateBoundAccessTokens(true)
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
|
||||
.clientSettings(clientSettings)
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
// @formatter:on
|
||||
@@ -134,8 +125,7 @@ public class OAuth2AccessTokenGeneratorTests {
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
@@ -179,10 +169,6 @@ public class OAuth2AccessTokenGeneratorTests {
|
||||
Set<String> scopes = accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE);
|
||||
assertThat(scopes).isEqualTo(tokenContext.getAuthorizedScopes());
|
||||
|
||||
Map<String, Object> cnf = accessTokenClaims.getClaim("cnf");
|
||||
assertThat(cnf).isNotEmpty();
|
||||
assertThat(cnf.get("x5t#S256")).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2TokenClaimsContext> tokenClaimsContextCaptor = ArgumentCaptor.forClass(OAuth2TokenClaimsContext.class);
|
||||
verify(this.accessTokenCustomizer).customize(tokenClaimsContextCaptor.capture());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user