Browse Source
This commit also refactors OAuth2 client properties. With the added support for authorization_code clients, client registrations are now divided into `login` and `authorization_code`. An environment post processor is used for backward compatibility with old Open ID Connect login clients. Closes gh-13812pull/14230/merge
18 changed files with 723 additions and 191 deletions
@ -0,0 +1,115 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2018 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 |
||||||
|
* |
||||||
|
* http://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.boot.autoconfigure.security.oauth2.client; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.context.config.ConfigFileApplicationListener; |
||||||
|
import org.springframework.boot.context.properties.bind.Bindable; |
||||||
|
import org.springframework.boot.context.properties.bind.Binder; |
||||||
|
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; |
||||||
|
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; |
||||||
|
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; |
||||||
|
import org.springframework.boot.env.EnvironmentPostProcessor; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.core.env.ConfigurableEnvironment; |
||||||
|
import org.springframework.core.env.MapPropertySource; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link EnvironmentPostProcessor} that migrates legacy OAuth2 login client properties |
||||||
|
* under the `spring.security.oauth2.client.login` prefix. |
||||||
|
* |
||||||
|
* @author Madhura Bhave |
||||||
|
* @since 2.1.0 |
||||||
|
*/ |
||||||
|
public class OAuth2ClientPropertiesEnvironmentPostProcessor |
||||||
|
implements EnvironmentPostProcessor, Ordered { |
||||||
|
|
||||||
|
private static final Bindable<Map<String, OAuth2ClientProperties.LoginClientRegistration>> STRING_LEGACY_REGISTRATION_MAP = Bindable |
||||||
|
.mapOf(String.class, OAuth2ClientProperties.LoginClientRegistration.class); |
||||||
|
|
||||||
|
private static final String PREFIX = "spring.security.oauth2.client.registration"; |
||||||
|
|
||||||
|
private static final String LOGIN_REGISTRATION_PREFIX = PREFIX + ".login."; |
||||||
|
|
||||||
|
private static final String UPDATED_PROPERTY_SOURCE_SUFFIX = "-updated-oauth-client"; |
||||||
|
|
||||||
|
private int order = ConfigFileApplicationListener.DEFAULT_ORDER + 1; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void postProcessEnvironment(ConfigurableEnvironment environment, |
||||||
|
SpringApplication application) { |
||||||
|
environment.getPropertySources().forEach((propertySource) -> { |
||||||
|
String name = propertySource.getName(); |
||||||
|
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources |
||||||
|
.from(propertySource); |
||||||
|
ConfigurationPropertySource source = sources.iterator().next(); |
||||||
|
Binder binder = new Binder(sources); |
||||||
|
Map<String, Object> map = new LinkedHashMap<>(); |
||||||
|
MapPropertySource updatedPropertySource = new MapPropertySource( |
||||||
|
name + UPDATED_PROPERTY_SOURCE_SUFFIX, map); |
||||||
|
Map<String, OAuth2ClientProperties.LoginClientRegistration> registrations = binder |
||||||
|
.bind(PREFIX, STRING_LEGACY_REGISTRATION_MAP) |
||||||
|
.orElse(Collections.emptyMap()); |
||||||
|
registrations.entrySet() |
||||||
|
.forEach((entry) -> addProperties(entry, source, map)); |
||||||
|
if (!map.isEmpty()) { |
||||||
|
environment.getPropertySources().addBefore(name, updatedPropertySource); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void addProperties( |
||||||
|
Map.Entry<String, OAuth2ClientProperties.LoginClientRegistration> entry, |
||||||
|
ConfigurationPropertySource source, Map<String, Object> map) { |
||||||
|
OAuth2ClientProperties.LoginClientRegistration registration = entry.getValue(); |
||||||
|
String registrationId = entry.getKey(); |
||||||
|
addProperty(registrationId, "client-id", registration::getClientId, map, source); |
||||||
|
addProperty(registrationId, "client-secret", registration::getClientSecret, map, |
||||||
|
source); |
||||||
|
addProperty(registrationId, "client-name", registration::getClientName, map, |
||||||
|
source); |
||||||
|
addProperty(registrationId, "redirect-uri-template", |
||||||
|
registration::getRedirectUriTemplate, map, source); |
||||||
|
addProperty(registrationId, "authorization-grant-type", |
||||||
|
registration::getAuthorizationGrantType, map, source); |
||||||
|
addProperty(registrationId, "client-authentication-method", |
||||||
|
registration::getClientAuthenticationMethod, map, source); |
||||||
|
addProperty(registrationId, "provider", registration::getProvider, map, source); |
||||||
|
addProperty(registrationId, "scope", registration::getScope, map, source); |
||||||
|
} |
||||||
|
|
||||||
|
private void addProperty(String registrationId, String property, |
||||||
|
Supplier<Object> valueSupplier, Map<String, Object> map, |
||||||
|
ConfigurationPropertySource source) { |
||||||
|
String registrationKey = PREFIX + "." + registrationId + "."; |
||||||
|
String loginRegistrationKey = LOGIN_REGISTRATION_PREFIX + registrationId + "."; |
||||||
|
if (source.getConfigurationProperty( |
||||||
|
ConfigurationPropertyName.of(registrationKey + property)) != null) { |
||||||
|
map.put(loginRegistrationKey + property, valueSupplier.get()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getOrder() { |
||||||
|
return this.order; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,158 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2018 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 |
||||||
|
* |
||||||
|
* http://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.boot.autoconfigure.security.oauth2.client; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.core.env.MapPropertySource; |
||||||
|
import org.springframework.core.env.MutablePropertySources; |
||||||
|
import org.springframework.core.env.StandardEnvironment; |
||||||
|
import org.springframework.core.env.SystemEnvironmentPropertySource; |
||||||
|
import org.springframework.mock.env.MockEnvironment; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link OAuth2ClientPropertiesEnvironmentPostProcessor}. |
||||||
|
* |
||||||
|
* @author Madhura Bhave |
||||||
|
*/ |
||||||
|
public class OAuth2ClientPropertiesEnvironmentPostProcessorTests { |
||||||
|
|
||||||
|
private OAuth2ClientPropertiesEnvironmentPostProcessor postProcessor = new OAuth2ClientPropertiesEnvironmentPostProcessor(); |
||||||
|
|
||||||
|
private MockEnvironment environment; |
||||||
|
|
||||||
|
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration.github-client."; |
||||||
|
|
||||||
|
private static final String ENVIRONMENT_REGISTRATION_PREFIX = "SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GITHUB-CLIENT_"; |
||||||
|
|
||||||
|
private static final String LOGIN_REGISTRATION_PREFIX = "spring.security.oauth2.client.registration.login.github-client."; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
this.environment = new MockEnvironment(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void postProcessorWhenLegacyPropertiesShouldConvert() { |
||||||
|
Map<String, Object> properties = new HashMap<>(); |
||||||
|
properties.put(REGISTRATION_PREFIX + "client-id", "my-client-id"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "client-secret", "my-client-secret"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "redirect-uri-template", |
||||||
|
"http://my-redirect-uri.com"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "provider", "github"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "scope", "user"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "client-name", "my-client-name"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "authorization-grant-type", |
||||||
|
"authorization_code"); |
||||||
|
properties.put(REGISTRATION_PREFIX + "client-authentication-method", "FORM"); |
||||||
|
MapPropertySource source = new MapPropertySource("test", properties); |
||||||
|
this.environment.getPropertySources().addFirst(source); |
||||||
|
this.postProcessor.postProcessEnvironment(this.environment, null); |
||||||
|
assertPropertyMigration(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void postProcessorDoesNotCopyMissingProperties() { |
||||||
|
Map<String, Object> properties = new HashMap<>(); |
||||||
|
properties.put(REGISTRATION_PREFIX + "client-id", "my-client-id"); |
||||||
|
MapPropertySource source = new MapPropertySource("test", properties); |
||||||
|
this.environment.getPropertySources().addFirst(source); |
||||||
|
this.postProcessor.postProcessEnvironment(this.environment, null); |
||||||
|
assertThat(this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "client-id")) |
||||||
|
.isEqualTo("my-client-id"); |
||||||
|
assertThat( |
||||||
|
this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "client-secret")) |
||||||
|
.isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void postProcessorWhenLegacyEnvironmentVariablesPropertiesShouldConvert() { |
||||||
|
Map<String, Object> properties = new HashMap<>(); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "CLIENTID", "my-client-id"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "CLIENTSECRET", |
||||||
|
"my-client-secret"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "REDIRECTURITEMPLATE", |
||||||
|
"http://my-redirect-uri.com"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "PROVIDER", "github"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "SCOPE", "user"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "CLIENTNAME", "my-client-name"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "AUTHORIZATIONGRANTTYPE", |
||||||
|
"authorization_code"); |
||||||
|
properties.put(ENVIRONMENT_REGISTRATION_PREFIX + "CLIENTAUTHENTICATIONMETHOD", |
||||||
|
"FORM"); |
||||||
|
SystemEnvironmentPropertySource source = new SystemEnvironmentPropertySource( |
||||||
|
"test-" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, |
||||||
|
properties); |
||||||
|
this.environment.getPropertySources().addFirst(source); |
||||||
|
this.postProcessor.postProcessEnvironment(this.environment, null); |
||||||
|
assertPropertyMigration(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void postProcessorWhenNewPropertiesShouldDoNothing() { |
||||||
|
Map<String, Object> properties = new HashMap<>(); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "client-id", "my-client-id"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "client-secret", "my-client-secret"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "redirect-uri-template", |
||||||
|
"http://my-redirect-uri.com"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "provider", "github"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "scope", "user"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "client-name", "my-client-name"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "authorization-grant-type", |
||||||
|
"authorization_code"); |
||||||
|
properties.put(LOGIN_REGISTRATION_PREFIX + "client-authentication-method", |
||||||
|
"FORM"); |
||||||
|
MapPropertySource source = new MapPropertySource("test", properties); |
||||||
|
this.environment.getPropertySources().addFirst(source); |
||||||
|
MutablePropertySources propertySources = new MutablePropertySources( |
||||||
|
this.environment.getPropertySources()); |
||||||
|
this.postProcessor.postProcessEnvironment(this.environment, null); |
||||||
|
assertPropertyMigration(); |
||||||
|
assertThat(this.environment.getPropertySources()) |
||||||
|
.containsExactlyElementsOf(propertySources); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertPropertyMigration() { |
||||||
|
assertThat(this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "client-id")) |
||||||
|
.isEqualTo("my-client-id"); |
||||||
|
assertThat( |
||||||
|
this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "client-secret")) |
||||||
|
.isEqualTo("my-client-secret"); |
||||||
|
assertThat(this.environment |
||||||
|
.getProperty(LOGIN_REGISTRATION_PREFIX + "redirect-uri-template")) |
||||||
|
.isEqualTo("http://my-redirect-uri.com"); |
||||||
|
assertThat(this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "provider")) |
||||||
|
.isEqualTo("github"); |
||||||
|
assertThat(this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "scope")) |
||||||
|
.isEqualTo("user"); |
||||||
|
assertThat( |
||||||
|
this.environment.getProperty(LOGIN_REGISTRATION_PREFIX + "client-name")) |
||||||
|
.isEqualTo("my-client-name"); |
||||||
|
assertThat(this.environment |
||||||
|
.getProperty(LOGIN_REGISTRATION_PREFIX + "authorization-grant-type")) |
||||||
|
.isEqualTo("authorization_code"); |
||||||
|
assertThat(this.environment |
||||||
|
.getProperty(LOGIN_REGISTRATION_PREFIX + "client-authentication-method")) |
||||||
|
.isEqualTo("FORM"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue