Allow configurable scope validation strategy in OAuth2ClientCredentialsAuthenticationProvider
Closes gh-1377
This commit is contained in:
committed by
Joe Grandja
parent
168077be24
commit
5c3f1cb691
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.authentication;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientCredentialsAuthenticationContext}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2ClientCredentialsAuthenticationContextTests {
|
||||
private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient).build();
|
||||
private final Authentication principal = this.authorization.getAttribute(Principal.class.getName());
|
||||
private final OAuth2ClientCredentialsAuthenticationToken authorizationConsentAuthentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(this.principal, Set.of("a_scope"), Map.of("a_key", "a_value"));
|
||||
|
||||
@Test
|
||||
public void withWhenAuthenticationNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2ClientCredentialsAuthenticationContext.with(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authentication cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setWhenValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2ClientCredentialsAuthenticationContext.Builder builder =
|
||||
OAuth2ClientCredentialsAuthenticationContext.with(this.authorizationConsentAuthentication);
|
||||
|
||||
assertThatThrownBy(() -> builder.registeredClient(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> builder.put(null, ""))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenRequiredValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2ClientCredentialsAuthenticationContext.Builder builder =
|
||||
OAuth2ClientCredentialsAuthenticationContext.with(this.authorizationConsentAuthentication);
|
||||
assertThatThrownBy(builder::build)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClient cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
|
||||
OAuth2ClientCredentialsAuthenticationContext context =
|
||||
OAuth2ClientCredentialsAuthenticationContext.with(this.authorizationConsentAuthentication)
|
||||
.registeredClient(this.registeredClient)
|
||||
.put("custom-key-1", "custom-value-1")
|
||||
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
|
||||
.build();
|
||||
|
||||
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.authorizationConsentAuthentication);
|
||||
assertThat(context.getRegisteredClient()).isEqualTo(this.registeredClient);
|
||||
assertThat(context.<String>get("custom-key-1")).isEqualTo("custom-value-1");
|
||||
assertThat(context.<String>get("custom-key-2")).isEqualTo("custom-value-2");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,16 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
@@ -56,6 +50,12 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -211,6 +211,16 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(requestedScope);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthenticationValidatorThenInvokeValidator() {
|
||||
Consumer<OAuth2ClientCredentialsAuthenticationContext> validator = mock(Consumer.class);
|
||||
this.authenticationProvider.setAuthenticationValidator(validator);
|
||||
|
||||
authenticateWhenScopeRequestedThenAccessTokenContainsScope();
|
||||
|
||||
verify(validator).accept(any(OAuth2ClientCredentialsAuthenticationContext.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenNoScopeRequestedThenAccessTokenDoesNotContainScope() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients.SCOPE_1;
|
||||
import static org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients.SCOPE_2;
|
||||
|
||||
public class OAuth2ClientCredentialsAuthenticationValidatorTest {
|
||||
private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient).build();
|
||||
private final Authentication principal = this.authorization.getAttribute(Principal.class.getName());
|
||||
private final OAuth2ClientCredentialsAuthenticationValidator validator = new OAuth2ClientCredentialsAuthenticationValidator();
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("validScopes")
|
||||
public void acceptWhenRequestScopesAreEmptyOrValidThenDoesNotThrowException(Set<String> testScopes) {
|
||||
OAuth2ClientCredentialsAuthenticationToken token =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(this.principal, testScopes, Map.of());
|
||||
OAuth2ClientCredentialsAuthenticationContext context = OAuth2ClientCredentialsAuthenticationContext.with(token).registeredClient(registeredClient).build();
|
||||
|
||||
assertThatNoException().isThrownBy(() -> validator.accept(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenRequestScopesAreNotAllValidThenThrowException() {
|
||||
OAuth2ClientCredentialsAuthenticationToken token =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(this.principal, Set.of(SCOPE_1, SCOPE_2), Map.of());
|
||||
OAuth2ClientCredentialsAuthenticationContext context = OAuth2ClientCredentialsAuthenticationContext.with(token).registeredClient(registeredClient).build();
|
||||
|
||||
assertThatThrownBy(() -> validator.accept(context))
|
||||
.isInstanceOfSatisfying(OAuth2ClientCredentialsAuthenticationException.class,
|
||||
t -> assertThat(t.getClientCredentialsAuthentication()).isEqualTo(token));
|
||||
}
|
||||
|
||||
static Stream<Arguments> validScopes() {
|
||||
return Stream.of(Arguments.of(new HashSet<>()), Arguments.of(Set.of(SCOPE_1)));
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,19 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.client;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* @author Anoop Garlapati
|
||||
*/
|
||||
public class TestRegisteredClients {
|
||||
public static final String SCOPE_1 = "scope1";
|
||||
public static final String SCOPE_2 = "scope2";
|
||||
|
||||
public static RegisteredClient.Builder registeredClient() {
|
||||
return RegisteredClient.withId("registration-1")
|
||||
@@ -39,7 +41,7 @@ public class TestRegisteredClients {
|
||||
.redirectUri("https://example.com/callback-2")
|
||||
.redirectUri("https://example.com/callback-3")
|
||||
.postLogoutRedirectUri("https://example.com/oidc-post-logout")
|
||||
.scope("scope1");
|
||||
.scope(SCOPE_1);
|
||||
}
|
||||
|
||||
public static RegisteredClient.Builder registeredClient2() {
|
||||
@@ -54,8 +56,8 @@ public class TestRegisteredClients {
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
||||
.redirectUri("https://example.com")
|
||||
.postLogoutRedirectUri("https://example.com/oidc-post-logout")
|
||||
.scope("scope1")
|
||||
.scope("scope2");
|
||||
.scope(SCOPE_1)
|
||||
.scope(SCOPE_2);
|
||||
}
|
||||
|
||||
public static RegisteredClient.Builder registeredPublicClient() {
|
||||
@@ -65,7 +67,7 @@ public class TestRegisteredClients {
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
|
||||
.redirectUri("https://example.com")
|
||||
.scope("scope1")
|
||||
.scope(SCOPE_1)
|
||||
.clientSettings(ClientSettings.builder().requireProofKey(true).build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
@@ -39,7 +28,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -111,6 +99,15 @@ import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
Reference in New Issue
Block a user