Support authorization_code grant for OAuth2 client

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-13812
This commit is contained in:
Madhura Bhave 2018-08-22 14:04:10 -07:00
parent 5af7835e83
commit f5deebf0cb
18 changed files with 725 additions and 193 deletions

View File

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -37,29 +38,44 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
*/ */
public class ClientsConfiguredCondition extends SpringBootCondition { public class ClientsConfiguredCondition extends SpringBootCondition {
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable private static final Bindable<Map<String, OAuth2ClientProperties.LoginClientRegistration>> STRING_LOGIN_REGISTRATION_MAP = Bindable
.mapOf(String.class, OAuth2ClientProperties.Registration.class); .mapOf(String.class, OAuth2ClientProperties.LoginClientRegistration.class);
private static final Bindable<Map<String, OAuth2ClientProperties.AuthorizationCodeClientRegistration>> STRING_AUTHORIZATIONCODE_REGISTRATION_MAP = Bindable
.mapOf(String.class,
OAuth2ClientProperties.AuthorizationCodeClientRegistration.class);
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth2 Clients Configured Condition"); .forCondition("OAuth2 Clients Configured Condition");
Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations( Map<String, OAuth2ClientProperties.BaseClientRegistration> registrations = getRegistrations(
context.getEnvironment()); context.getEnvironment());
if (!registrations.isEmpty()) { if (!registrations.isEmpty()) {
return ConditionOutcome.match(message return ConditionOutcome.match(message.foundExactly(
.foundExactly("registered clients " + registrations.values().stream() "registered clients " + registrations.values().stream().map(
.map(OAuth2ClientProperties.Registration::getClientId) OAuth2ClientProperties.BaseClientRegistration::getClientId)
.collect(Collectors.joining(", ")))); .collect(Collectors.joining(", "))));
} }
return ConditionOutcome.noMatch(message.notAvailable("registered clients")); return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
} }
private Map<String, OAuth2ClientProperties.Registration> getRegistrations( private Map<String, OAuth2ClientProperties.BaseClientRegistration> getRegistrations(
Environment environment) { Environment environment) {
return Binder.get(environment).bind("spring.security.oauth2.client.registration", Map<String, OAuth2ClientProperties.BaseClientRegistration> registrations = new HashMap();
STRING_REGISTRATION_MAP).orElse(Collections.emptyMap()); Map<String, OAuth2ClientProperties.LoginClientRegistration> loginClientRegistrations = Binder
.get(environment).bind("spring.security.oauth2.client.registration.login",
STRING_LOGIN_REGISTRATION_MAP)
.orElse(Collections.emptyMap());
Map<String, OAuth2ClientProperties.AuthorizationCodeClientRegistration> authCodeClientRegistrations = Binder
.get(environment)
.bind("spring.security.oauth2.client.registration.authorizationcode",
STRING_AUTHORIZATIONCODE_REGISTRATION_MAP)
.orElse(Collections.emptyMap());
registrations.putAll(loginClientRegistrations);
registrations.putAll(authCodeClientRegistrations);
return registrations;
} }
} }

View File

@ -44,32 +44,106 @@ public class OAuth2ClientProperties {
/** /**
* OAuth client registrations. * OAuth client registrations.
*/ */
private final Map<String, Registration> registration = new HashMap<>(); private final Registration registration = new Registration();
public Map<String, Provider> getProvider() { public Map<String, Provider> getProvider() {
return this.provider; return this.provider;
} }
public Map<String, Registration> getRegistration() { public Registration getRegistration() {
return this.registration; return this.registration;
} }
@PostConstruct @PostConstruct
public void validate() { public void validate() {
this.getRegistration().values().forEach(this::validateRegistration); this.getRegistration().getLogin().values().forEach(this::validateRegistration);
this.getRegistration().getAuthorizationCode().values()
.forEach(this::validateRegistration);
} }
private void validateRegistration(Registration registration) { private void validateRegistration(BaseClientRegistration registration) {
if (!StringUtils.hasText(registration.getClientId())) { if (!StringUtils.hasText(registration.getClientId())) {
throw new IllegalStateException("Client id must not be empty."); throw new IllegalStateException("Client id must not be empty.");
} }
} }
/**
* A single client registration.
*/
public static class Registration { public static class Registration {
/**
* OpenID Connect client registrations.
*/
private Map<String, LoginClientRegistration> login = new HashMap<>();
/**
* OAuth2 authorization_code client registrations.
*/
private Map<String, AuthorizationCodeClientRegistration> authorizationCode = new HashMap<>();
public Map<String, LoginClientRegistration> getLogin() {
return this.login;
}
public void setLogin(Map<String, LoginClientRegistration> login) {
this.login = login;
}
public Map<String, AuthorizationCodeClientRegistration> getAuthorizationCode() {
return this.authorizationCode;
}
public void setAuthorizationCode(
Map<String, AuthorizationCodeClientRegistration> authorizationCode) {
this.authorizationCode = authorizationCode;
}
}
/**
* A single client registration for OpenID Connect login.
*/
public static class LoginClientRegistration extends BaseClientRegistration {
/**
* Redirect URI. May be left blank when using a pre-defined provider.
*/
private String redirectUriTemplate;
public String getRedirectUriTemplate() {
return this.redirectUriTemplate;
}
public void setRedirectUriTemplate(String redirectUriTemplate) {
this.redirectUriTemplate = redirectUriTemplate;
}
}
/**
* A single client registration for OAuth2 authorization_code flow.
*/
public static class AuthorizationCodeClientRegistration
extends BaseClientRegistration {
/**
* Redirect URI for the registration.
*/
private String redirectUri;
public String getRedirectUri() {
return this.redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
}
/**
* Base class for a single client registration.
*/
public static class BaseClientRegistration {
/** /**
* Reference to the OAuth 2.0 provider to use. May reference an element from the * Reference to the OAuth 2.0 provider to use. May reference an element from the
* 'provider' property or used one of the commonly used providers (google, github, * 'provider' property or used one of the commonly used providers (google, github,
@ -98,11 +172,6 @@ public class OAuth2ClientProperties {
*/ */
private String authorizationGrantType; private String authorizationGrantType;
/**
* Redirect URI. May be left blank when using a pre-defined provider.
*/
private String redirectUriTemplate;
/** /**
* Authorization scopes. May be left blank when using a pre-defined provider. * Authorization scopes. May be left blank when using a pre-defined provider.
*/ */
@ -153,14 +222,6 @@ public class OAuth2ClientProperties {
this.authorizationGrantType = authorizationGrantType; this.authorizationGrantType = authorizationGrantType;
} }
public String getRedirectUriTemplate() {
return this.redirectUriTemplate;
}
public void setRedirectUriTemplate(String redirectUriTemplate) {
this.redirectUriTemplate = redirectUriTemplate;
}
public Set<String> getScope() { public Set<String> getScope() {
return this.scope; return this.scope;
} }

View File

@ -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;
}
}

View File

@ -20,7 +20,6 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
@ -51,19 +50,35 @@ public final class OAuth2ClientPropertiesRegistrationAdapter {
public static Map<String, ClientRegistration> getClientRegistrations( public static Map<String, ClientRegistration> getClientRegistrations(
OAuth2ClientProperties properties) { OAuth2ClientProperties properties) {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>(); Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
properties.getRegistration().forEach((key, value) -> clientRegistrations.put(key, properties.getRegistration().getLogin()
getClientRegistration(key, value, properties.getProvider()))); .forEach((key, value) -> clientRegistrations.put(key,
getLoginClientRegistration(key, value,
properties.getProvider())));
properties.getRegistration().getAuthorizationCode()
.forEach((key, value) -> clientRegistrations.put(key,
getAuthorizationCodeClientRegistration(key, value,
properties.getProvider())));
return clientRegistrations; return clientRegistrations;
} }
private static ClientRegistration getClientRegistration(String registrationId, private static ClientRegistration getAuthorizationCodeClientRegistration(
Registration properties, Map<String, Provider> providers) { String registrationId,
OAuth2ClientProperties.AuthorizationCodeClientRegistration properties,
Map<String, Provider> providers) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Builder builder = getBuilder(map, registrationId, properties, providers);
map.from(properties::getRedirectUri).to(builder::redirectUriTemplate);
return builder.build();
}
private static Builder getBuilder(PropertyMapper map, String registrationId,
OAuth2ClientProperties.BaseClientRegistration properties,
Map<String, Provider> providers) {
Builder builder = getBuilderFromIssuerIfPossible(registrationId, Builder builder = getBuilderFromIssuerIfPossible(registrationId,
properties.getProvider(), providers); properties.getProvider(), providers);
if (builder == null) { if (builder == null) {
builder = getBuilder(registrationId, properties.getProvider(), providers); builder = getBuilder(registrationId, properties.getProvider(), providers);
} }
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getClientId).to(builder::clientId); map.from(properties::getClientId).to(builder::clientId);
map.from(properties::getClientSecret).to(builder::clientSecret); map.from(properties::getClientSecret).to(builder::clientSecret);
map.from(properties::getClientAuthenticationMethod) map.from(properties::getClientAuthenticationMethod)
@ -71,10 +86,18 @@ public final class OAuth2ClientPropertiesRegistrationAdapter {
.to(builder::clientAuthenticationMethod); .to(builder::clientAuthenticationMethod);
map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new) map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new)
.to(builder::authorizationGrantType); .to(builder::authorizationGrantType);
map.from(properties::getRedirectUriTemplate).to(builder::redirectUriTemplate);
map.from(properties::getScope).as((scope) -> StringUtils.toStringArray(scope)) map.from(properties::getScope).as((scope) -> StringUtils.toStringArray(scope))
.to(builder::scope); .to(builder::scope);
map.from(properties::getClientName).to(builder::clientName); map.from(properties::getClientName).to(builder::clientName);
return builder;
}
private static ClientRegistration getLoginClientRegistration(String registrationId,
OAuth2ClientProperties.LoginClientRegistration properties,
Map<String, Provider> providers) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
Builder builder = getBuilder(map, registrationId, properties, providers);
map.from(properties::getRedirectUriTemplate).to(builder::redirectUriTemplate);
return builder.build(); return builder.build();
} }

View File

@ -60,7 +60,8 @@ class OAuth2WebSecurityConfiguration {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().oauth2Login(); http.authorizeRequests().anyRequest().authenticated().and().oauth2Login()
.and().oauth2Client().authorizationCodeGrant();
} }
} }

View File

@ -546,6 +546,15 @@
"name": "spring.session.hazelcast.flush-mode", "name": "spring.session.hazelcast.flush-mode",
"defaultValue": "on-save" "defaultValue": "on-save"
}, },
{
"name" : "spring.security.oauth2.client.registration",
"type" : "java.util.Map<java.lang.String,org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.LoginClientRegistration>",
"description" : "Maps client registration-id to a client registration.",
"deprecation" : {
"replacement" : "spring.security.oauth2.client.registration.login",
"level" : "warning"
}
},
{ {
"name": "spring.session.servlet.filter-dispatcher-types", "name": "spring.session.servlet.filter-dispatcher-types",
"defaultValue": [ "defaultValue": [

View File

@ -7,6 +7,10 @@ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingL
org.springframework.context.ApplicationListener=\ org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesEnvironmentPostProcessor
# Auto Configuration Import Listeners # Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

View File

@ -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");
}
}

View File

@ -28,8 +28,8 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.LoginClientRegistration;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -72,17 +72,17 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
provider.setUserInfoAuthenticationMethod("form"); provider.setUserInfoAuthenticationMethod("form");
provider.setUserNameAttribute("sub"); provider.setUserNameAttribute("sub");
provider.setJwkSetUri("http://example.com/jwk"); provider.setJwkSetUri("http://example.com/jwk");
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setProvider("provider"); login.setProvider("provider");
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post"); login.setClientAuthenticationMethod("post");
registration.setAuthorizationGrantType("authorization_code"); login.setAuthorizationGrantType("authorization_code");
registration.setRedirectUriTemplate("http://example.com/redirect"); login.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("scope")); login.setScope(Collections.singleton("scope"));
registration.setClientName("clientName"); login.setClientName("clientName");
properties.getRegistration().getLogin().put("registration", login);
properties.getProvider().put("provider", provider); properties.getProvider().put("provider", provider);
properties.getRegistration().put("registration", registration);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration"); ClientRegistration adapted = registrations.get("registration");
@ -114,11 +114,11 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test @Test
public void getClientRegistrationsWhenUsingCommonProviderShouldAdapt() { public void getClientRegistrationsWhenUsingCommonProviderShouldAdapt() {
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setProvider("google"); login.setProvider("google");
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
properties.getRegistration().put("registration", registration); properties.getRegistration().getLogin().put("registration", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration"); ClientRegistration adapted = registrations.get("registration");
@ -149,16 +149,16 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test @Test
public void getClientRegistrationsWhenUsingCommonProviderWithOverrideShouldAdapt() { public void getClientRegistrationsWhenUsingCommonProviderWithOverrideShouldAdapt() {
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setProvider("google"); login.setProvider("google");
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post"); login.setClientAuthenticationMethod("post");
registration.setAuthorizationGrantType("authorization_code"); login.setAuthorizationGrantType("authorization_code");
registration.setRedirectUriTemplate("http://example.com/redirect"); login.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("scope")); login.setScope(Collections.singleton("scope"));
registration.setClientName("clientName"); login.setClientName("clientName");
properties.getRegistration().put("registration", registration); properties.getRegistration().getLogin().put("registration", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration"); ClientRegistration adapted = registrations.get("registration");
@ -192,9 +192,9 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test @Test
public void getClientRegistrationsWhenUnknownProviderShouldThrowException() { public void getClientRegistrationsWhenUnknownProviderShouldThrowException() {
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setProvider("missing"); login.setProvider("missing");
properties.getRegistration().put("registration", registration); properties.getRegistration().getLogin().put("registration", login);
this.thrown.expect(IllegalStateException.class); this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Unknown provider ID 'missing'"); this.thrown.expectMessage("Unknown provider ID 'missing'");
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties); OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties);
@ -203,10 +203,10 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test @Test
public void getClientRegistrationsWhenProviderNotSpecifiedShouldUseRegistrationId() { public void getClientRegistrationsWhenProviderNotSpecifiedShouldUseRegistrationId() {
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
properties.getRegistration().put("google", registration); properties.getRegistration().getLogin().put("google", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("google"); ClientRegistration adapted = registrations.get("google");
@ -235,11 +235,47 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
assertThat(adapted.getClientName()).isEqualTo("Google"); assertThat(adapted.getClientName()).isEqualTo("Google");
} }
@Test
public void getClientRegistrationsWhenAuhtorizationCodeClientShouldAdapt() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
OAuth2ClientProperties.AuthorizationCodeClientRegistration registration = new OAuth2ClientProperties.AuthorizationCodeClientRegistration();
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
registration.setRedirectUri("http://my-redirect-uri.com");
properties.getRegistration().getAuthorizationCode().put("google", registration);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("google");
ProviderDetails adaptedProvider = adapted.getProviderDetails();
assertThat(adaptedProvider.getAuthorizationUri())
.isEqualTo("https://accounts.google.com/o/oauth2/v2/auth");
assertThat(adaptedProvider.getTokenUri())
.isEqualTo("https://www.googleapis.com/oauth2/v4/token");
assertThat(adaptedProvider.getUserInfoEndpoint().getUri())
.isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo");
assertThat(adaptedProvider.getUserInfoEndpoint().getAuthenticationMethod())
.isEqualTo(
org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
assertThat(adaptedProvider.getJwkSetUri())
.isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
assertThat(adapted.getRegistrationId()).isEqualTo("google");
assertThat(adapted.getClientId()).isEqualTo("clientId");
assertThat(adapted.getClientSecret()).isEqualTo("clientSecret");
assertThat(adapted.getRedirectUriTemplate())
.isEqualTo("http://my-redirect-uri.com");
assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(
org.springframework.security.oauth2.core.ClientAuthenticationMethod.BASIC);
assertThat(adapted.getAuthorizationGrantType()).isEqualTo(
org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(adapted.getScopes()).containsExactly("openid", "profile", "email");
assertThat(adapted.getClientName()).isEqualTo("Google");
}
@Test @Test
public void getClientRegistrationsWhenProviderNotSpecifiedAndUnknownProviderShouldThrowException() { public void getClientRegistrationsWhenProviderNotSpecifiedAndUnknownProviderShouldThrowException() {
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
properties.getRegistration().put("missing", registration); properties.getRegistration().getLogin().put("missing", login);
this.thrown.expect(IllegalStateException.class); this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage( this.thrown.expectMessage(
"Provider ID must be specified for client registration 'missing'"); "Provider ID must be specified for client registration 'missing'");
@ -249,20 +285,20 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test @Test
public void oidcProviderConfigurationWhenProviderNotSpecifiedOnRegistration() public void oidcProviderConfigurationWhenProviderNotSpecifiedOnRegistration()
throws Exception { throws Exception {
Registration registration = new Registration(); LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
testOidcConfiguration(registration, "okta"); testOidcConfiguration(login, "okta");
} }
@Test @Test
public void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration() public void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration()
throws Exception { throws Exception {
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new LoginClientRegistration();
registration.setProvider("okta-oidc"); login.setProvider("okta-oidc");
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
testOidcConfiguration(registration, "okta-oidc"); testOidcConfiguration(login, "okta-oidc");
} }
@Test @Test
@ -273,13 +309,13 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
String issuer = this.server.url("").toString(); String issuer = this.server.url("").toString();
String cleanIssuerPath = cleanIssuerPath(issuer); String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath); setupMockResponse(cleanIssuerPath);
Registration registration = new Registration(); OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
registration.setProvider("okta-oidc"); login.setProvider("okta-oidc");
registration.setClientId("clientId"); login.setClientId("clientId");
registration.setClientSecret("clientSecret"); login.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post"); login.setClientAuthenticationMethod("post");
registration.setRedirectUriTemplate("http://example.com/redirect"); login.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("user")); login.setScope(Collections.singleton("user"));
Provider provider = new Provider(); Provider provider = new Provider();
provider.setIssuerUri(issuer); provider.setIssuerUri(issuer);
provider.setAuthorizationUri("http://example.com/auth"); provider.setAuthorizationUri("http://example.com/auth");
@ -289,7 +325,7 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
provider.setJwkSetUri("http://example.com/jwk"); provider.setJwkSetUri("http://example.com/jwk");
OAuth2ClientProperties properties = new OAuth2ClientProperties(); OAuth2ClientProperties properties = new OAuth2ClientProperties();
properties.getProvider().put("okta-oidc", provider); properties.getProvider().put("okta-oidc", provider);
properties.getRegistration().put("okta", registration); properties.getRegistration().getLogin().put("okta", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("okta"); ClientRegistration adapted = registrations.get("okta");
@ -313,8 +349,9 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
.isEqualTo("sub"); .isEqualTo("sub");
} }
private void testOidcConfiguration(Registration registration, String providerId) private void testOidcConfiguration(
throws Exception { OAuth2ClientProperties.LoginClientRegistration registration,
String providerId) throws Exception {
this.server = new MockWebServer(); this.server = new MockWebServer();
this.server.start(); this.server.start();
String issuer = this.server.url("").toString(); String issuer = this.server.url("").toString();
@ -324,7 +361,7 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
Provider provider = new Provider(); Provider provider = new Provider();
provider.setIssuerUri(issuer); provider.setIssuerUri(issuer);
properties.getProvider().put(providerId, provider); properties.getProvider().put(providerId, provider);
properties.getRegistration().put("okta", registration); properties.getRegistration().getLogin().put("okta", registration);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties); .getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("okta"); ClientRegistration adapted = registrations.get("okta");

View File

@ -20,6 +20,9 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.AuthorizationCodeClientRegistration;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.LoginClientRegistration;
/** /**
* Tests for {@link OAuth2ClientProperties}. * Tests for {@link OAuth2ClientProperties}.
* *
@ -34,11 +37,11 @@ public class OAuth2ClientPropertiesTests {
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@Test @Test
public void clientIdAbsentThrowsException() { public void clientIdAbsentForLoginClientsThrowsException() {
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); LoginClientRegistration registration = new LoginClientRegistration();
registration.setClientSecret("secret"); registration.setClientSecret("secret");
registration.setProvider("google"); registration.setProvider("google");
this.properties.getRegistration().put("foo", registration); this.properties.getRegistration().getLogin().put("foo", registration);
this.thrown.expect(IllegalStateException.class); this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Client id must not be empty."); this.thrown.expectMessage("Client id must not be empty.");
this.properties.validate(); this.properties.validate();
@ -46,10 +49,30 @@ public class OAuth2ClientPropertiesTests {
@Test @Test
public void clientSecretAbsentShouldNotThrowException() { public void clientSecretAbsentShouldNotThrowException() {
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); LoginClientRegistration registration = new LoginClientRegistration();
registration.setClientId("foo"); registration.setClientId("foo");
registration.setProvider("google"); registration.setProvider("google");
this.properties.getRegistration().put("foo", registration); this.properties.getRegistration().getLogin().put("foo", registration);
this.properties.validate();
}
@Test
public void clientIdAbsentForAuthorizationCodeClientsThrowsException() {
AuthorizationCodeClientRegistration registration = new AuthorizationCodeClientRegistration();
registration.setClientSecret("secret");
registration.setProvider("google");
this.properties.getRegistration().getAuthorizationCode().put("foo", registration);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Client id must not be empty.");
this.properties.validate();
}
@Test
public void clientSecretAbsentForAuthorizationCodeClientDoesNotThrowException() {
AuthorizationCodeClientRegistration registration = new AuthorizationCodeClientRegistration();
registration.setClientId("foo");
registration.setProvider("google");
this.properties.getRegistration().getAuthorizationCode().put("foo", registration);
this.properties.validate(); this.properties.validate();
} }

View File

@ -37,7 +37,7 @@ public class ReactiveOAuth2ClientAutoConfigurationTests {
.withConfiguration( .withConfiguration(
AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class)); AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class));
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration.login";
@Test @Test
public void autoConfigurationShouldImportConfigurations() { public void autoConfigurationShouldImportConfigurations() {

View File

@ -33,7 +33,7 @@ public class ReactiveOAuth2ClientRegistrationRepositoryConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration.login";
@Test @Test
public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() { public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {

View File

@ -33,7 +33,7 @@ public class OAuth2ClientRegistrationRepositoryConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration.login";
@Test @Test
public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() { public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {

View File

@ -39,7 +39,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -68,7 +68,28 @@ public class OAuth2WebSecurityConfigurationTests {
ClientRegistrationRepository expected = context ClientRegistrationRepository expected = context
.getBean(ClientRegistrationRepository.class); .getBean(ClientRegistrationRepository.class);
ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils
.getField(getAuthCodeFilters(context).get(0), .getField(
getFilters(context,
OAuth2LoginAuthenticationFilter.class).get(0),
"clientRegistrationRepository");
assertThat(isEqual(expected.findByRegistrationId("first"),
actual.findByRegistrationId("first"))).isTrue();
assertThat(isEqual(expected.findByRegistrationId("second"),
actual.findByRegistrationId("second"))).isTrue();
});
}
@Test
public void securityConfigurerConfiguresAuthorizationCode() {
this.contextRunner
.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class,
OAuth2WebSecurityConfiguration.class)
.run((context) -> {
ClientRegistrationRepository expected = context
.getBean(ClientRegistrationRepository.class);
ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils
.getField(getFilters(context,
OAuth2AuthorizationCodeGrantFilter.class).get(0),
"clientRegistrationRepository"); "clientRegistrationRepository");
assertThat(isEqual(expected.findByRegistrationId("first"), assertThat(isEqual(expected.findByRegistrationId("first"),
actual.findByRegistrationId("first"))).isTrue(); actual.findByRegistrationId("first"))).isTrue();
@ -79,10 +100,14 @@ public class OAuth2WebSecurityConfigurationTests {
@Test @Test
public void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() { public void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() {
this.contextRunner this.contextRunner.withUserConfiguration(TestConfig.class,
.withUserConfiguration(TestConfig.class, OAuth2WebSecurityConfiguration.class).run((context) -> {
OAuth2WebSecurityConfiguration.class) assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class))
.run((context) -> assertThat(getAuthCodeFilters(context)).isEmpty()); .isEmpty();
assertThat(
getFilters(context, OAuth2AuthorizationCodeGrantFilter.class))
.isEmpty();
});
} }
@Test @Test
@ -107,7 +132,11 @@ public class OAuth2WebSecurityConfigurationTests {
public void securityConfigurerBacksOffWhenOtherWebSecurityAdapterPresent() { public void securityConfigurerBacksOffWhenOtherWebSecurityAdapterPresent() {
this.contextRunner.withUserConfiguration(TestWebSecurityConfigurerConfig.class, this.contextRunner.withUserConfiguration(TestWebSecurityConfigurerConfig.class,
OAuth2WebSecurityConfiguration.class).run((context) -> { OAuth2WebSecurityConfiguration.class).run((context) -> {
assertThat(getAuthCodeFilters(context)).isEmpty(); assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class))
.isEmpty();
assertThat(
getFilters(context, OAuth2AuthorizationCodeGrantFilter.class))
.isEmpty();
assertThat(context).getBean(OAuth2AuthorizedClientService.class) assertThat(context).getBean(OAuth2AuthorizedClientService.class)
.isNotNull(); .isNotNull();
}); });
@ -137,19 +166,14 @@ public class OAuth2WebSecurityConfigurationTests {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<Filter> getAuthCodeFilters(AssertableApplicationContext context) { private List<Filter> getFilters(AssertableApplicationContext context,
Class<? extends Filter> filter) {
FilterChainProxy filterChain = (FilterChainProxy) context FilterChainProxy filterChain = (FilterChainProxy) context
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
List<SecurityFilterChain> filterChains = filterChain.getFilterChains(); List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
List<Filter> filters = (List<Filter>) ReflectionTestUtils List<Filter> filters = (List<Filter>) ReflectionTestUtils
.getField(filterChains.get(0), "filters"); .getField(filterChains.get(0), "filters");
List<Filter> oauth2Filters = filters.stream() return filters.stream().filter(filter::isInstance).collect(Collectors.toList());
.filter((f) -> f instanceof OAuth2LoginAuthenticationFilter
|| f instanceof OAuth2AuthorizationRequestRedirectFilter)
.collect(Collectors.toList());
return oauth2Filters.stream()
.filter((f) -> f instanceof OAuth2LoginAuthenticationFilter)
.collect(Collectors.toList());
} }
private boolean isEqual(ClientRegistration reg1, ClientRegistration reg2) { private boolean isEqual(ClientRegistration reg1, ClientRegistration reg2) {

View File

@ -3219,36 +3219,18 @@ https://oauth.net/2/[OAuth2] is a widely used authorization framework that is su
Spring. Spring.
[[boot-features-security-oauth2-client]] [[boot-features-security-oauth2-client]]
==== Client ==== Client
If you have `spring-security-oauth2-client` on your classpath, you can take advantage of If you have `spring-security-oauth2-client` on your classpath, you can take advantage of
some auto-configuration to make it easy to set up an OAuth2 Client. This configuration some auto-configuration to make it easy to set up an OAuth2/Open ID Connect clients. This configuration
makes use of the properties under `OAuth2ClientProperties`. The same properties are applicable makes use of the properties under `OAuth2ClientProperties`.
for both servlet and reactive applications.
You can register multiple OAuth2 clients and providers under the You can register multiple OAuth2/OpenID Connect providers under the `spring.security.oauth2.client.provider`
`spring.security.oauth2.client` prefix, as shown in the following example: prefix, as shown in the following example:
[source,properties,indent=0] [source,properties,indent=0]
---- ----
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri-template=http://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri-template=http://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=http://my-auth-server/oauth/authorize spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=http://my-auth-server/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=http://my-auth-server/oauth/token spring.security.oauth2.client.provider.my-oauth-provider.token-uri=http://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=http://my-auth-server/userinfo spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=http://my-auth-server/userinfo
@ -3257,6 +3239,48 @@ You can register multiple OAuth2 clients and providers under the
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
---- ----
For OpenID Connect providers that support https://openid.net/specs/openid-connect-discovery-1_0.html[OpenID Connect discovery],
the configuration can be further simplified. The provider needs to be configured with an `issuer-uri` which is the
URI that the it asserts as its Issuer Identifier. For example, if the
`issuer-uri` provided is "https://example.com", then an `OpenID Provider Configuration Request`
will be made to "https://example.com/.well-known/openid-configuration". The result is expected
to be an `OpenID Provider Configuration Response`. The following example shows how an OpenID Connect
Provider can be configured with the `issuer-uri`:
[source,properties,indent=0]
----
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
----
[[boot-features-security-oauth2-login-client-registration]]
===== OpenID Connect Login client registration
You can register multiple Open ID Connect clients under the
`spring.security.oauth2.client.registration.login` prefix, as shown in the following example:
[source,properties,indent=0]
----
spring.security.oauth2.client.registration.login.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.login.my-client-1.client-secret=password
spring.security.oauth2.client.registration.login.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.login.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.login.my-client-1.scope=user
spring.security.oauth2.client.registration.login.my-client-1.redirect-uri-template=http://localhost:8080/login/oauth2/code/my-client-1
spring.security.oauth2.client.registration.login.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.login.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.login.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.login.my-client-2.client-secret=password
spring.security.oauth2.client.registration.login.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.login.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.login.my-client-2.scope=email
spring.security.oauth2.client.registration.login.my-client-2.redirect-uri-template=http://localhost:8080/login/oauth2/code/my-client-2
spring.security.oauth2.client.registration.login.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.login.my-client-2.authorization-grant-type=authorization_code
----
By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs
matching `/login/oauth2/code/*`. If you want to customize the `redirect-uri-template` to matching `/login/oauth2/code/*`. If you want to customize the `redirect-uri-template` to
use a different pattern, you need to provide configuration to process that custom pattern. use a different pattern, you need to provide configuration to process that custom pattern.
@ -3280,6 +3304,40 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
} }
---- ----
The same properties are applicable to both servlet and reactive applications.
[[boot-features-security-oauth2-authorization-code-client-registration]]
===== OAuth2 Authorization Code client registration
You can register multiple OAuth2 `authorization_code` clients under the
`spring.security.oauth2.client.registration.authorization-code` prefix, as shown in the following example:
[source,properties,indent=0]
----
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-secret=password
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.authorization-code.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.authorization-code.my-client-1.scope=user
spring.security.oauth2.client.registration.authorization-code.my-client-1.redirect-uri=http://my-redirect-uri.com
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.authorization-code.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-secret=password
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.authorization-code.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.authorization-code.my-client-2.scope=email
spring.security.oauth2.client.registration.authorization-code.my-client-2.redirect-uri=http://my-redirect-uri.com
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.authorization-code.my-client-2.authorization-grant-type=authorization_code
----
[[boot-features-security-oauth2-common-providers]]
===== OAuth2 client registration for common providers
For common OAuth2 and OpenID providers, including Google, Github, Facebook, and Okta, For common OAuth2 and OpenID providers, including Google, Github, Facebook, and Okta,
we provide a set of provider defaults (`google`, `github`, `facebook`, and `okta`, we provide a set of provider defaults (`google`, `github`, `facebook`, and `okta`,
respectively). respectively).
@ -3292,27 +3350,12 @@ In other words, the two configurations in the following example use the Google p
[source,properties,indent=0] [source,properties,indent=0]
---- ----
spring.security.oauth2.client.registration.my-client.client-id=abcd spring.security.oauth2.client.registration.login.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password spring.security.oauth2.client.registration.login.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google spring.security.oauth2.client.registration.login.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd spring.security.oauth2.client.registration.login.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password spring.security.oauth2.client.registration.login.google.client-secret=password
----
For OpenID Connect providers that support https://openid.net/specs/openid-connect-discovery-1_0.html[OpenID Connect discovery],
the configuration can be further simplified. The provider needs to be configured with an `issuer-uri` which is the
URI that the it asserts as its Issuer Identifier. For example, if the
`issuer-uri` provided is "https://example.com", then an `OpenID Provider Configuration Request`
will be made to "https://example.com/.well-known/openid-configuration". The result is expected
to be an `OpenID Provider Configuration Response`. The following example shows how an OpenID Connect
Provider can be configured with the `issuer-uri`:
[source,properties,indent=0]
----
spring.security.oauth2.client.registration.oidc-provider.client-id=abcd
spring.security.oauth2.client.registration.oidc-provider.client-secret=password
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
---- ----

View File

@ -3,23 +3,32 @@ spring:
oauth2: oauth2:
client: client:
registration: registration:
github-client-1: login:
client-id: ${APP-CLIENT-ID} github-client-1:
client-secret: ${APP-CLIENT-SECRET} client-id: ${APP-CLIENT-ID}
client-name: Github user client-secret: ${APP-CLIENT-SECRET}
provider: github client-name: Github user
scope: user provider: github
redirect-uri-template: http://localhost:8080/login/oauth2/code/github scope: user
github-client-2: redirect-uri-template: http://localhost:8080/login/oauth2/code/github
client-id: ${APP-CLIENT-ID} github-client-2:
client-secret: ${APP-CLIENT-SECRET} client-id: ${APP-CLIENT-ID}
client-name: Github email client-secret: ${APP-CLIENT-SECRET}
provider: github client-name: Github email
scope: user:email provider: github
redirect-uri-template: http://localhost:8080/login/oauth2/code/github scope: user:email
yahoo-oidc: redirect-uri-template: http://localhost:8080/login/oauth2/code/github
client-id: ${YAHOO-CLIENT-ID} yahoo-oidc:
client-secret: ${YAHOO-CLIENT-SECRET} client-id: a
client-secret: b
authorization_code:
github-repos:
client-id: ${APP-CLIENT-ID}
client-secret: ${APP-CLIENT-SECRET}
scope: public_repo
redirect-uri: "{baseUrl}/github-repos"
provider: github
client-name: GitHub Repositories
provider: provider:
yahoo-oidc: yahoo-oidc:
issuer-uri: https://api.login.yahoo.com/ issuer-uri: https://api.login.yahoo.com/

View File

@ -53,7 +53,7 @@ public class SampleOAuth2ClientApplicationTests {
} }
@Test @Test
public void loginShouldHaveBothOAuthClientsToChooseFrom() { public void loginShouldHaveBothOAuth2LoginClientsToChooseFrom() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/login", ResponseEntity<String> entity = this.restTemplate.getForEntity("/login",
String.class); String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@ -62,4 +62,12 @@ public class SampleOAuth2ClientApplicationTests {
assertThat(entity.getBody()).contains("/oauth2/authorization/github-client-2"); assertThat(entity.getBody()).contains("/oauth2/authorization/github-client-2");
} }
@Test
public void authorizationCodeClientIsPresent() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/login",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("/oauth2/authorization/github-repos");
}
} }

View File

@ -3,23 +3,24 @@ spring:
oauth2: oauth2:
client: client:
registration: registration:
github-client-1: login:
client-id: ${APP-CLIENT-ID} github-client-1:
client-secret: ${APP-CLIENT-SECRET} client-id: ${APP-CLIENT-ID}
client-name: Github user client-secret: ${APP-CLIENT-SECRET}
provider: github client-name: Github user
scope: user provider: github
redirect-uri-template: http://localhost:8080/login/oauth2/code/github scope: user
github-client-2: redirect-uri-template: http://localhost:8080/login/oauth2/code/github
client-id: ${APP-CLIENT-ID} github-client-2:
client-secret: ${APP-CLIENT-SECRET} client-id: ${APP-CLIENT-ID}
client-name: Github email client-secret: ${APP-CLIENT-SECRET}
provider: github client-name: Github email
scope: user:email provider: github
redirect-uri-template: http://localhost:8080/login/oauth2/code/github scope: user:email
yahoo-oidc: redirect-uri-template: http://localhost:8080/login/oauth2/code/github
client-id: ${YAHOO-CLIENT-ID} yahoo-oidc:
client-secret: ${YAHOO-CLIENT-SECRET} client-id: ${YAHOO-CLIENT-ID}
client-secret: ${YAHOO-CLIENT-SECRET}
provider: provider:
yahoo-oidc: yahoo-oidc:
issuer-uri: https://api.login.yahoo.com/ issuer-uri: https://api.login.yahoo.com/