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,105 @@
|
||||
/*
|
||||
* 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.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2ClientCredentialsAuthenticationToken} and additional information
|
||||
* and is used when validating the OAuth 2.0 Authorization Request used in the Client Credentials Grant.
|
||||
*
|
||||
* @author Adam Pilling
|
||||
* @since 1.3.0
|
||||
* @see OAuth2AuthenticationContext
|
||||
* @see OAuth2ClientCredentialsAuthenticationToken
|
||||
* @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer)
|
||||
*/
|
||||
public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth2AuthenticationContext {
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
private OAuth2ClientCredentialsAuthenticationContext(Map<Object, Object> context) {
|
||||
this.context = Map.copyOf(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public <V> V get(Object key) {
|
||||
return hasKey(key) ? (V) this.context.get(key) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(Object key) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
return this.context.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @return the {@link RegisteredClient}
|
||||
*/
|
||||
public RegisteredClient getRegisteredClient() {
|
||||
return get(RegisteredClient.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2ClientCredentialsAuthenticationToken}.
|
||||
*
|
||||
* @param authentication the {@link OAuth2ClientCredentialsAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder with(OAuth2ClientCredentialsAuthenticationToken authentication) {
|
||||
return new Builder(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link OAuth2ClientCredentialsAuthenticationContext}.
|
||||
*/
|
||||
public static final class Builder extends AbstractBuilder<OAuth2ClientCredentialsAuthenticationContext, Builder> {
|
||||
|
||||
private Builder(OAuth2ClientCredentialsAuthenticationToken authentication) {
|
||||
super(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @param registeredClient the {@link RegisteredClient}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder registeredClient(RegisteredClient registeredClient) {
|
||||
return put(RegisteredClient.class, registeredClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2ClientCredentialsAuthenticationContext}.
|
||||
*
|
||||
* @return the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
*/
|
||||
public OAuth2ClientCredentialsAuthenticationContext build() {
|
||||
Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null");
|
||||
return new OAuth2ClientCredentialsAuthenticationContext(getContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link OAuth2ClientCredentialsAuthenticationProvider}
|
||||
* when an attempt to authenticate the OAuth 2.0 Authorization Request (or Consent) fails.
|
||||
*
|
||||
* @author Adam Pilling
|
||||
* @since 1.3.0
|
||||
* @see OAuth2ClientCredentialsAuthenticationToken
|
||||
* @see OAuth2ClientCredentialsAuthenticationProvider
|
||||
*/
|
||||
public class OAuth2ClientCredentialsAuthenticationException extends OAuth2AuthenticationException {
|
||||
private final OAuth2ClientCredentialsAuthenticationToken authorizationCodeRequestAuthentication;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2ClientCredentialsAuthenticationException} using the provided parameters.
|
||||
*
|
||||
* @param error the {@link OAuth2Error OAuth 2.0 Error}
|
||||
* @param authorizationCodeRequestAuthentication the {@link Authentication} instance of the OAuth 2.0 Authorization Request (or Consent)
|
||||
*/
|
||||
public OAuth2ClientCredentialsAuthenticationException(
|
||||
OAuth2Error error,
|
||||
@Nullable OAuth2ClientCredentialsAuthenticationToken authorizationCodeRequestAuthentication) {
|
||||
super(error);
|
||||
this.authorizationCodeRequestAuthentication = authorizationCodeRequestAuthentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Authentication} instance of the OAuth 2.0 Authorization Request (or Consent), or {@code null} if not available.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
*/
|
||||
@Nullable
|
||||
public OAuth2ClientCredentialsAuthenticationToken getClientCredentialsAuthentication() {
|
||||
return this.authorizationCodeRequestAuthentication;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,13 +15,8 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -41,7 +36,9 @@ import org.springframework.security.oauth2.server.authorization.token.DefaultOAu
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||
|
||||
@@ -63,6 +60,8 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
private Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator =
|
||||
new OAuth2ClientCredentialsAuthenticationValidator();
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters.
|
||||
@@ -96,20 +95,18 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
|
||||
Set<String> authorizedScopes = Collections.emptySet();
|
||||
if (!CollectionUtils.isEmpty(clientCredentialsAuthentication.getScopes())) {
|
||||
for (String requestedScope : clientCredentialsAuthentication.getScopes()) {
|
||||
if (!registeredClient.getScopes().contains(requestedScope)) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
|
||||
}
|
||||
}
|
||||
authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
|
||||
}
|
||||
OAuth2ClientCredentialsAuthenticationContext authenticationContext =
|
||||
OAuth2ClientCredentialsAuthenticationContext.with(clientCredentialsAuthentication)
|
||||
.registeredClient(registeredClient)
|
||||
.build();
|
||||
authenticationValidator.accept(authenticationContext);
|
||||
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Validated token request parameters");
|
||||
}
|
||||
|
||||
Set<String> authorizedScopes = Set.copyOf(clientCredentialsAuthentication.getScopes());
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
@@ -168,4 +165,22 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
return OAuth2ClientCredentialsAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
* and is responsible for validating specific OAuth 2.0 Client Credentials parameters
|
||||
* associated in the {@link OAuth2ClientCredentialsAuthenticationToken}.
|
||||
* The default authentication validator is {@link OAuth2ClientCredentialsAuthenticationValidator}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2ClientCredentialsAuthenticationException} if validation fails.
|
||||
*
|
||||
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
* and is responsible for validating specific OAuth 2.0 Authorization Request parameters
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public void setAuthenticationValidator(Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator) {
|
||||
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
|
||||
this.authenticationValidator = authenticationValidator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
* containing an {@link OAuth2ClientCredentialsAuthenticationToken}
|
||||
* and is the default {@link OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer) authentication validator}
|
||||
* used for validating specific OAuth 2.0 Client Credentials parameters used in the Client Credentials Grant.
|
||||
*
|
||||
* <p>
|
||||
* The default compares the provided scopes with those configured in the RegisteredClient.
|
||||
* If validation fails, an {@link OAuth2ClientCredentialsAuthenticationException} is thrown.
|
||||
*
|
||||
* @author Adam Pilling
|
||||
* @since 1.3.0
|
||||
* @see OAuth2ClientCredentialsAuthenticationContext
|
||||
* @see RegisteredClient
|
||||
* @see OAuth2ClientCredentialsAuthenticationToken
|
||||
* @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer)
|
||||
*/
|
||||
public final class OAuth2ClientCredentialsAuthenticationValidator implements Consumer<OAuth2ClientCredentialsAuthenticationContext> {
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
|
||||
private static final Log LOGGER = LogFactory.getLog(OAuth2ClientCredentialsAuthenticationValidator.class);
|
||||
|
||||
/**
|
||||
* The default validator for {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}.
|
||||
*/
|
||||
public static final Consumer<OAuth2ClientCredentialsAuthenticationContext> DEFAULT_SCOPE_VALIDATOR =
|
||||
OAuth2ClientCredentialsAuthenticationValidator::validateScope;
|
||||
|
||||
private final Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator = DEFAULT_SCOPE_VALIDATOR;
|
||||
|
||||
@Override
|
||||
public void accept(OAuth2ClientCredentialsAuthenticationContext authenticationContext) {
|
||||
this.authenticationValidator.accept(authenticationContext);
|
||||
}
|
||||
|
||||
private static void validateScope(OAuth2ClientCredentialsAuthenticationContext authenticationContext) {
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthenticationToken =
|
||||
authenticationContext.getAuthentication();
|
||||
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
|
||||
|
||||
Set<String> requestedScopes = clientCredentialsAuthenticationToken.getScopes();
|
||||
Set<String> allowedScopes = registeredClient.getScopes();
|
||||
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(LogMessage.format("Invalid request: requested scope is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
}
|
||||
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, clientCredentialsAuthenticationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName,
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthenticationToken) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI);
|
||||
OAuth2ClientCredentialsAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(
|
||||
(Authentication) clientCredentialsAuthenticationToken.getPrincipal(),
|
||||
clientCredentialsAuthenticationToken.getScopes(),
|
||||
clientCredentialsAuthenticationToken.getAdditionalParameters());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
|
||||
throw new OAuth2ClientCredentialsAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
@@ -217,7 +216,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
return authenticationConverters;
|
||||
}
|
||||
|
||||
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
|
||||
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
|
||||
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
|
||||
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity);
|
||||
|
||||
@@ -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