From d302444650739111144268458c070ff007619366 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 29 Nov 2021 02:58:08 -0500 Subject: [PATCH] Introduce ProviderContext Closes gh-479 --- .../OAuth2AuthorizationServerConfigurer.java | 15 +-- .../authorization/OAuth2ConfigurerUtils.java | 7 +- .../server/authorization/OidcConfigurer.java | 19 ++-- ...thorizationCodeAuthenticationProvider.java | 10 +- ...ientCredentialsAuthenticationProvider.java | 10 +- ...th2RefreshTokenAuthenticationProvider.java | 10 +- .../context/ProviderContext.java | 70 ++++++++++++ .../context/ProviderContextHolder.java | 63 +++++++++++ ...entRegistrationAuthenticationProvider.java | 16 +-- ...dcProviderConfigurationEndpointFilter.java | 13 ++- ...orizationServerMetadataEndpointFilter.java | 17 +-- .../web/ProviderContextFilter.java | 86 +++++++++++++++ ...zationCodeAuthenticationProviderTests.java | 15 ++- ...redentialsAuthenticationProviderTests.java | 17 ++- ...freshTokenAuthenticationProviderTests.java | 13 ++- ...gistrationAuthenticationProviderTests.java | 24 +++-- ...viderConfigurationEndpointFilterTests.java | 15 ++- ...tionServerMetadataEndpointFilterTests.java | 15 ++- .../web/ProviderContextFilterTests.java | 101 ++++++++++++++++++ 19 files changed, 459 insertions(+), 77 deletions(-) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContext.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContextHolder.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilter.java create mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilterTests.java diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java index 493b8f13..c4e66c7f 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -46,12 +46,14 @@ import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSet import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; +import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; @@ -341,6 +343,9 @@ public final class OAuth2AuthorizationServerConfigurer, AbstractOAuth2Configurer> createConfigurers() { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java index 6d91d110..5495c6c9 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -122,10 +122,7 @@ final class OAuth2ConfigurerUtils { static > ProviderSettings getProviderSettings(B builder) { ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class); if (providerSettings == null) { - providerSettings = getOptionalBean(builder, ProviderSettings.class); - if (providerSettings == null) { - providerSettings = ProviderSettings.builder().build(); - } + providerSettings = getBean(builder, ProviderSettings.class); builder.setSharedObject(ProviderSettings.class, providerSettings); } return providerSettings; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java index dd17955a..560a27b1 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -85,16 +85,13 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer { } List requestMatchers = new ArrayList<>(); - ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); - if (providerSettings.getIssuer() != null) { - requestMatchers.add(new AntPathRequestMatcher( - "/.well-known/openid-configuration", HttpMethod.GET.name())); - } + requestMatchers.add(new AntPathRequestMatcher( + "/.well-known/openid-configuration", HttpMethod.GET.name())); requestMatchers.add(this.userInfoEndpointConfigurer.getRequestMatcher()); if (this.clientRegistrationEndpointConfigurer != null) { requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher()); } - this.requestMatcher = requestMatchers.size() > 1 ? new OrRequestMatcher(requestMatchers) : requestMatchers.get(0); + this.requestMatcher = new OrRequestMatcher(requestMatchers); } @Override @@ -105,11 +102,9 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer { } ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); - if (providerSettings.getIssuer() != null) { - OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = - new OidcProviderConfigurationEndpointFilter(providerSettings); - builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); - } + OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = + new OidcProviderConfigurationEndpointFilter(providerSettings); + builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); } @Override diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java index 0541c9fe..ed18bfde 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -26,7 +26,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -55,6 +54,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -87,7 +87,6 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth private final JwtEncoder jwtEncoder; private OAuth2TokenCustomizer jwtCustomizer = (context) -> {}; private Supplier refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey; - private ProviderSettings providerSettings; /** * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters. @@ -124,9 +123,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth this.refreshTokenGenerator = refreshTokenGenerator; } - @Autowired + @Deprecated protected void setProviderSettings(ProviderSettings providerSettings) { - this.providerSettings = providerSettings; } @Override @@ -167,7 +165,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); } - String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null; + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); Set authorizedScopes = authorization.getAttribute( OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java index 8cbead05..e23c33f6 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -19,7 +19,6 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Consumer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -38,6 +37,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -62,7 +62,6 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth private final OAuth2AuthorizationService authorizationService; private final JwtEncoder jwtEncoder; private OAuth2TokenCustomizer jwtCustomizer = (context) -> {}; - private ProviderSettings providerSettings; /** * Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters. @@ -90,9 +89,8 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth this.jwtCustomizer = jwtCustomizer; } - @Autowired + @Deprecated protected void setProviderSettings(ProviderSettings providerSettings) { - this.providerSettings = providerSettings; } @Override @@ -118,7 +116,7 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes()); } - String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null; + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); JoseHeader.Builder headersBuilder = JwtUtils.headers(); JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims( diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java index bb56bb41..0f2a2125 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -26,7 +26,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -52,6 +51,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenCusto import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; import org.springframework.security.oauth2.server.authorization.config.TokenSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.util.Assert; import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient; @@ -80,7 +80,6 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic private final JwtEncoder jwtEncoder; private OAuth2TokenCustomizer jwtCustomizer = (context) -> {}; private Supplier refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey; - private ProviderSettings providerSettings; /** * Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters. @@ -118,9 +117,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic this.refreshTokenGenerator = refreshTokenGenerator; } - @Autowired + @Deprecated protected void setProviderSettings(ProviderSettings providerSettings) { - this.providerSettings = providerSettings; } @Override @@ -166,7 +164,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic scopes = authorizedScopes; } - String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null; + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); JoseHeader.Builder headersBuilder = JwtUtils.headers(); JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims( diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContext.java new file mode 100644 index 00000000..fec5bc8f --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContext.java @@ -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.context; + +import java.util.function.Supplier; + +import org.springframework.lang.Nullable; +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.util.Assert; + +/** + * A context that holds information of the Provider. + * + * @author Joe Grandja + * @since 0.2.2 + * @see ProviderSettings + * @see ProviderContextHolder + */ +public final class ProviderContext { + private final ProviderSettings providerSettings; + private final Supplier issuerSupplier; + + /** + * Constructs a {@code ProviderContext} using the provided parameters. + * + * @param providerSettings the provider settings + * @param issuerSupplier a {@code Supplier} for the {@code URL} of the Provider's issuer identifier + */ + public ProviderContext(ProviderSettings providerSettings, @Nullable Supplier issuerSupplier) { + Assert.notNull(providerSettings, "providerSettings cannot be null"); + this.providerSettings = providerSettings; + this.issuerSupplier = issuerSupplier; + } + + /** + * Returns the {@link ProviderSettings}. + * + * @return the {@link ProviderSettings} + */ + public ProviderSettings getProviderSettings() { + return this.providerSettings; + } + + /** + * Returns the {@code URL} of the Provider's issuer identifier. + * The issuer identifier is resolved from the constructor parameter {@code Supplier} + * or if not provided then defaults to {@link ProviderSettings#getIssuer()}. + * + * @return the {@code URL} of the Provider's issuer identifier + */ + public String getIssuer() { + return this.issuerSupplier != null ? + this.issuerSupplier.get() : + getProviderSettings().getIssuer(); + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContextHolder.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContextHolder.java new file mode 100644 index 00000000..dfeac7dd --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/ProviderContextHolder.java @@ -0,0 +1,63 @@ +/* + * 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.context; + +import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter; + +/** + * A holder of {@link ProviderContext} that associates it with the current thread using a {@code ThreadLocal}. + * + * @author Joe Grandja + * @since 0.2.2 + * @see ProviderContext + * @see ProviderContextFilter + */ +public final class ProviderContextHolder { + private static final ThreadLocal holder = new ThreadLocal<>(); + + private ProviderContextHolder() { + } + + /** + * Returns the {@link ProviderContext} bound to the current thread. + * + * @return the {@link ProviderContext} + */ + public static ProviderContext getProviderContext() { + return holder.get(); + } + + /** + * Bind the given {@link ProviderContext} to the current thread. + * + * @param providerContext the {@link ProviderContext} + */ + public static void setProviderContext(ProviderContext providerContext) { + if (providerContext == null) { + resetProviderContext(); + } else { + holder.set(providerContext); + } + } + + /** + * Reset the {@link ProviderContext} bound to the current thread. + */ + public static void resetProviderContext() { + holder.remove(); + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java index 234931f2..1c50279b 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -56,6 +56,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.config.ClientSettings; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; import org.springframework.security.oauth2.server.authorization.config.TokenSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -85,7 +87,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe private final RegisteredClientRepository registeredClientRepository; private final OAuth2AuthorizationService authorizationService; private JwtEncoder jwtEncoder; - private ProviderSettings providerSettings; /** * Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters. @@ -126,9 +127,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe this.jwtEncoder = jwtEncoder; } - @Autowired + @Deprecated protected void setProviderSettings(ProviderSettings providerSettings) { - this.providerSettings = providerSettings; } @Override @@ -233,8 +233,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe authorizedScopes.add(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE); authorizedScopes = Collections.unmodifiableSet(authorizedScopes); + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); JwtClaimsSet claims = JwtUtils.accessTokenClaims( - registeredClient, this.providerSettings.getIssuer(), registeredClient.getClientId(), authorizedScopes) + registeredClient, issuer, registeredClient.getClientId(), authorizedScopes) .build(); Jwt registrationAccessToken = this.jwtEncoder.encode(headers, claims); @@ -283,8 +284,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe scopes.addAll(registeredClient.getScopes())); } - String registrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) - .path(this.providerSettings.getOidcClientRegistrationEndpoint()) + ProviderContext providerContext = ProviderContextHolder.getProviderContext(); + String registrationClientUri = UriComponentsBuilder.fromUriString(providerContext.getIssuer()) + .path(providerContext.getProviderSettings().getOidcClientRegistrationEndpoint()) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .toUriString(); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java index 9ae71db2..12507f07 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -35,6 +35,7 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; @@ -79,12 +80,14 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques return; } + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); + OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder() - .issuer(this.providerSettings.getIssuer()) - .authorizationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getAuthorizationEndpoint())) - .tokenEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint())) + .issuer(issuer) + .authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint())) + .tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint())) .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) - .jwkSetUrl(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getJwkSetEndpoint())) + .jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint())) .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java index f6ed5e94..efb690a6 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -33,6 +33,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadat import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.http.converter.OAuth2AuthorizationServerMetadataHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; @@ -77,19 +78,21 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP return; } + String issuer = ProviderContextHolder.getProviderContext().getIssuer(); + OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() - .issuer(this.providerSettings.getIssuer()) - .authorizationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getAuthorizationEndpoint())) - .tokenEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint())) + .issuer(issuer) + .authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint())) + .tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint())) .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) - .jwkSetUrl(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getJwkSetEndpoint())) + .jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint())) .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue()) - .tokenRevocationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenRevocationEndpoint())) + .tokenRevocationEndpoint(asUrl(issuer, this.providerSettings.getTokenRevocationEndpoint())) .tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods()) - .tokenIntrospectionEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenIntrospectionEndpoint())) + .tokenIntrospectionEndpoint(asUrl(issuer, this.providerSettings.getTokenIntrospectionEndpoint())) .tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods()) .codeChallengeMethod("plain") .codeChallengeMethod("S256") diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilter.java new file mode 100644 index 00000000..87abbf0a --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilter.java @@ -0,0 +1,86 @@ +/* + * 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.web; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * A {@code Filter} that associates the {@link ProviderContext} to the {@link ProviderContextHolder}. + * + * @author Joe Grandja + * @since 0.2.2 + * @see ProviderContext + * @see ProviderContextHolder + * @see ProviderSettings + */ +public final class ProviderContextFilter extends OncePerRequestFilter { + private final ProviderSettings providerSettings; + + /** + * Constructs a {@code ProviderContextFilter} using the provided parameters. + * + * @param providerSettings the provider settings + */ + public ProviderContextFilter(ProviderSettings providerSettings) { + Assert.notNull(providerSettings, "providerSettings cannot be null"); + this.providerSettings = providerSettings; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + try { + ProviderContext providerContext = new ProviderContext( + this.providerSettings, () -> resolveIssuer(this.providerSettings, request)); + ProviderContextHolder.setProviderContext(providerContext); + filterChain.doFilter(request, response); + } finally { + ProviderContextHolder.resetProviderContext(); + } + } + + private static String resolveIssuer(ProviderSettings providerSettings, HttpServletRequest request) { + return providerSettings.getIssuer() != null ? + providerSettings.getIssuer() : + getContextPath(request); + } + + private static String getContextPath(HttpServletRequest request) { + // @formatter:off + return UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) + .replacePath(request.getContextPath()) + .replaceQuery(null) + .fragment(null) + .build() + .toUriString(); + // @formatter:on + } + +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java index a641f4e2..48a28deb 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Supplier; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -34,6 +35,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; @@ -48,13 +50,15 @@ import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; 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 org.springframework.security.oauth2.server.authorization.config.ProviderSettings; import org.springframework.security.oauth2.server.authorization.config.TokenSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -89,6 +93,13 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests { this.authorizationService, this.jwtEncoder); this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer); + ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); + } + + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java index c02321c4..e1e96a3f 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -20,6 +20,7 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -36,12 +37,15 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JoseHeaderNames; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; 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.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -70,6 +74,13 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests { this.authorizationService, this.jwtEncoder); this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer); + ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); + } + + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java index 08c90677..514a7320 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Supplier; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -52,7 +53,10 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenCusto 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 org.springframework.security.oauth2.server.authorization.config.ProviderSettings; import org.springframework.security.oauth2.server.authorization.config.TokenSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -88,6 +92,13 @@ public class OAuth2RefreshTokenAuthenticationProviderTests { this.authorizationService, this.jwtEncoder); this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer); + ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); + } + + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java index 6ac02163..aab87185 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -55,6 +56,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.config.ClientSettings; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.web.util.UriComponentsBuilder; @@ -87,10 +90,15 @@ public class OidcClientRegistrationAuthenticationProviderTests { this.registeredClientRepository = mock(RegisteredClientRepository.class); this.authorizationService = mock(OAuth2AuthorizationService.class); this.jwtEncoder = mock(JwtEncoder.class); - this.providerSettings = ProviderSettings.builder().issuer("https://auth-server:9000").build(); + this.providerSettings = ProviderSettings.builder().issuer("https://provider.com").build(); + ProviderContextHolder.setProviderContext(new ProviderContext(this.providerSettings, null)); this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider( this.registeredClientRepository, this.authorizationService, this.jwtEncoder); - this.authenticationProvider.setProviderSettings(this.providerSettings); + } + + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); } @Test @@ -549,8 +557,9 @@ public class OidcClientRegistrationAuthenticationProviderTests { assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) .isEqualTo(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) - .path(this.providerSettings.getOidcClientRegistrationEndpoint()) + ProviderContext providerContext = ProviderContextHolder.getProviderContext(); + String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(providerContext.getIssuer()) + .path(providerContext.getProviderSettings().getOidcClientRegistrationEndpoint()) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClientResult.getClientId()).toUriString(); assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl); @@ -744,8 +753,9 @@ public class OidcClientRegistrationAuthenticationProviderTests { assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) .isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) - .path(this.providerSettings.getOidcClientRegistrationEndpoint()) + ProviderContext providerContext = ProviderContextHolder.getProviderContext(); + String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(providerContext.getIssuer()) + .path(providerContext.getProviderSettings().getOidcClientRegistrationEndpoint()) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString(); assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java index 0186bf30..e38c3bce 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -19,12 +19,15 @@ import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.junit.After; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -41,6 +44,11 @@ import static org.mockito.Mockito.verifyNoInteractions; public class OidcProviderConfigurationEndpointFilterTests { private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); + } + @Test public void constructorWhenProviderSettingsNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() @@ -82,16 +90,18 @@ public class OidcProviderConfigurationEndpointFilterTests { @Test public void doFilterWhenConfigurationRequestThenConfigurationResponse() throws Exception { + String issuer = "https://example.com/issuer1"; String authorizationEndpoint = "/oauth2/v1/authorize"; String tokenEndpoint = "/oauth2/v1/token"; String jwkSetEndpoint = "/oauth2/v1/jwks"; ProviderSettings providerSettings = ProviderSettings.builder() - .issuer("https://example.com/issuer1") + .issuer(issuer) .authorizationEndpoint(authorizationEndpoint) .tokenEndpoint(tokenEndpoint) .jwkSetEndpoint(jwkSetEndpoint) .build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(providerSettings); @@ -124,6 +134,7 @@ public class OidcProviderConfigurationEndpointFilterTests { ProviderSettings providerSettings = ProviderSettings.builder() .issuer("https://this is an invalid URL") .build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(providerSettings); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java index 6a51d4d1..112f4447 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 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. @@ -19,12 +19,15 @@ import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.junit.After; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -41,6 +44,11 @@ import static org.mockito.Mockito.verifyNoInteractions; public class OAuth2AuthorizationServerMetadataEndpointFilterTests { private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); + } + @Test public void constructorWhenProviderSettingsNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() @@ -82,6 +90,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { @Test public void doFilterWhenAuthorizationServerMetadataRequestThenMetadataResponse() throws Exception { + String issuer = "https://example.com/issuer1"; String authorizationEndpoint = "/oauth2/v1/authorize"; String tokenEndpoint = "/oauth2/v1/token"; String jwkSetEndpoint = "/oauth2/v1/jwks"; @@ -89,13 +98,14 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { String tokenIntrospectionEndpoint = "/oauth2/v1/introspect"; ProviderSettings providerSettings = ProviderSettings.builder() - .issuer("https://example.com/issuer1") + .issuer(issuer) .authorizationEndpoint(authorizationEndpoint) .tokenEndpoint(tokenEndpoint) .jwkSetEndpoint(jwkSetEndpoint) .tokenRevocationEndpoint(tokenRevocationEndpoint) .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint) .build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings); @@ -130,6 +140,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { ProviderSettings providerSettings = ProviderSettings.builder() .issuer("https://this is an invalid URL") .build(); + ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null)); OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilterTests.java new file mode 100644 index 00000000..43218d71 --- /dev/null +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ProviderContextFilterTests.java @@ -0,0 +1,101 @@ +/* + * 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.web; + +import javax.servlet.FilterChain; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.context.ProviderContext; +import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ProviderContextFilter}. + * + * @author Joe Grandja + */ +public class ProviderContextFilterTests { + + @After + public void cleanup() { + ProviderContextHolder.resetProviderContext(); + } + + @Test + public void constructorWhenProviderSettingsNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new ProviderContextFilter(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("providerSettings cannot be null"); + } + + @Test + public void doFilterWhenIssuerConfiguredThenUsed() throws Exception { + String issuer = "https://provider.com"; + ProviderSettings providerSettings = ProviderSettings.builder().issuer(issuer).build(); + ProviderContextFilter filter = new ProviderContextFilter(providerSettings); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); + request.setServletPath("/"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + + doAnswer(invocation -> { + ProviderContext providerContext = ProviderContextHolder.getProviderContext(); + assertThat(providerContext).isNotNull(); + assertThat(providerContext.getProviderSettings()).isSameAs(providerSettings); + assertThat(providerContext.getIssuer()).isEqualTo(issuer); + return null; + }).when(filterChain).doFilter(any(), any()); + + filter.doFilter(request, response, filterChain); + + assertThat(ProviderContextHolder.getProviderContext()).isNull(); + } + + @Test + public void doFilterWhenIssuerNotConfiguredThenResolveFromRequest() throws Exception { + ProviderSettings providerSettings = ProviderSettings.builder().build(); + ProviderContextFilter filter = new ProviderContextFilter(providerSettings); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); + request.setServletPath("/"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + + doAnswer(invocation -> { + ProviderContext providerContext = ProviderContextHolder.getProviderContext(); + assertThat(providerContext).isNotNull(); + assertThat(providerContext.getProviderSettings()).isSameAs(providerSettings); + assertThat(providerContext.getIssuer()).isEqualTo("http://localhost"); + return null; + }).when(filterChain).doFilter(any(), any()); + + filter.doFilter(request, response, filterChain); + + assertThat(ProviderContextHolder.getProviderContext()).isNull(); + } + +}