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;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@ -37,29 +38,44 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
*/
public class ClientsConfiguredCondition extends SpringBootCondition {
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable
.mapOf(String.class, OAuth2ClientProperties.Registration.class);
private static final Bindable<Map<String, OAuth2ClientProperties.LoginClientRegistration>> STRING_LOGIN_REGISTRATION_MAP = Bindable
.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
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth2 Clients Configured Condition");
Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations(
Map<String, OAuth2ClientProperties.BaseClientRegistration> registrations = getRegistrations(
context.getEnvironment());
if (!registrations.isEmpty()) {
return ConditionOutcome.match(message
.foundExactly("registered clients " + registrations.values().stream()
.map(OAuth2ClientProperties.Registration::getClientId)
return ConditionOutcome.match(message.foundExactly(
"registered clients " + registrations.values().stream().map(
OAuth2ClientProperties.BaseClientRegistration::getClientId)
.collect(Collectors.joining(", "))));
}
return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
}
private Map<String, OAuth2ClientProperties.Registration> getRegistrations(
private Map<String, OAuth2ClientProperties.BaseClientRegistration> getRegistrations(
Environment environment) {
return Binder.get(environment).bind("spring.security.oauth2.client.registration",
STRING_REGISTRATION_MAP).orElse(Collections.emptyMap());
Map<String, OAuth2ClientProperties.BaseClientRegistration> registrations = new HashMap();
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.
*/
private final Map<String, Registration> registration = new HashMap<>();
private final Registration registration = new Registration();
public Map<String, Provider> getProvider() {
return this.provider;
}
public Map<String, Registration> getRegistration() {
public Registration getRegistration() {
return this.registration;
}
@PostConstruct
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())) {
throw new IllegalStateException("Client id must not be empty.");
}
}
/**
* A single client 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
* 'provider' property or used one of the commonly used providers (google, github,
@ -98,11 +172,6 @@ public class OAuth2ClientProperties {
*/
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.
*/
@ -153,14 +222,6 @@ public class OAuth2ClientProperties {
this.authorizationGrantType = authorizationGrantType;
}
public String getRedirectUriTemplate() {
return this.redirectUriTemplate;
}
public void setRedirectUriTemplate(String redirectUriTemplate) {
this.redirectUriTemplate = redirectUriTemplate;
}
public Set<String> getScope() {
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 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.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionException;
@ -51,19 +50,35 @@ public final class OAuth2ClientPropertiesRegistrationAdapter {
public static Map<String, ClientRegistration> getClientRegistrations(
OAuth2ClientProperties properties) {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
properties.getRegistration().forEach((key, value) -> clientRegistrations.put(key,
getClientRegistration(key, value, properties.getProvider())));
properties.getRegistration().getLogin()
.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;
}
private static ClientRegistration getClientRegistration(String registrationId,
Registration properties, Map<String, Provider> providers) {
private static ClientRegistration getAuthorizationCodeClientRegistration(
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,
properties.getProvider(), providers);
if (builder == null) {
builder = getBuilder(registrationId, properties.getProvider(), providers);
}
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(properties::getClientId).to(builder::clientId);
map.from(properties::getClientSecret).to(builder::clientSecret);
map.from(properties::getClientAuthenticationMethod)
@ -71,10 +86,18 @@ public final class OAuth2ClientPropertiesRegistrationAdapter {
.to(builder::clientAuthenticationMethod);
map.from(properties::getAuthorizationGrantType).as(AuthorizationGrantType::new)
.to(builder::authorizationGrantType);
map.from(properties::getRedirectUriTemplate).to(builder::redirectUriTemplate);
map.from(properties::getScope).as((scope) -> StringUtils.toStringArray(scope))
.to(builder::scope);
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();
}

View File

@ -60,7 +60,8 @@ class OAuth2WebSecurityConfiguration {
@Override
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",
"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",
"defaultValue": [

View File

@ -7,6 +7,10 @@ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingL
org.springframework.context.ApplicationListener=\
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
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
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.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.Registration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -72,17 +72,17 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
provider.setUserInfoAuthenticationMethod("form");
provider.setUserNameAttribute("sub");
provider.setJwkSetUri("http://example.com/jwk");
Registration registration = new Registration();
registration.setProvider("provider");
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post");
registration.setAuthorizationGrantType("authorization_code");
registration.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("scope"));
registration.setClientName("clientName");
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setProvider("provider");
login.setClientId("clientId");
login.setClientSecret("clientSecret");
login.setClientAuthenticationMethod("post");
login.setAuthorizationGrantType("authorization_code");
login.setRedirectUriTemplate("http://example.com/redirect");
login.setScope(Collections.singleton("scope"));
login.setClientName("clientName");
properties.getRegistration().getLogin().put("registration", login);
properties.getProvider().put("provider", provider);
properties.getRegistration().put("registration", registration);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration");
@ -114,11 +114,11 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test
public void getClientRegistrationsWhenUsingCommonProviderShouldAdapt() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration();
registration.setProvider("google");
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
properties.getRegistration().put("registration", registration);
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setProvider("google");
login.setClientId("clientId");
login.setClientSecret("clientSecret");
properties.getRegistration().getLogin().put("registration", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration");
@ -149,16 +149,16 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test
public void getClientRegistrationsWhenUsingCommonProviderWithOverrideShouldAdapt() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration();
registration.setProvider("google");
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post");
registration.setAuthorizationGrantType("authorization_code");
registration.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("scope"));
registration.setClientName("clientName");
properties.getRegistration().put("registration", registration);
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setProvider("google");
login.setClientId("clientId");
login.setClientSecret("clientSecret");
login.setClientAuthenticationMethod("post");
login.setAuthorizationGrantType("authorization_code");
login.setRedirectUriTemplate("http://example.com/redirect");
login.setScope(Collections.singleton("scope"));
login.setClientName("clientName");
properties.getRegistration().getLogin().put("registration", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("registration");
@ -192,9 +192,9 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test
public void getClientRegistrationsWhenUnknownProviderShouldThrowException() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration();
registration.setProvider("missing");
properties.getRegistration().put("registration", registration);
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setProvider("missing");
properties.getRegistration().getLogin().put("registration", login);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Unknown provider ID 'missing'");
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties);
@ -203,10 +203,10 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test
public void getClientRegistrationsWhenProviderNotSpecifiedShouldUseRegistrationId() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration();
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
properties.getRegistration().put("google", registration);
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setClientId("clientId");
login.setClientSecret("clientSecret");
properties.getRegistration().getLogin().put("google", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("google");
@ -235,11 +235,47 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
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
public void getClientRegistrationsWhenProviderNotSpecifiedAndUnknownProviderShouldThrowException() {
OAuth2ClientProperties properties = new OAuth2ClientProperties();
Registration registration = new Registration();
properties.getRegistration().put("missing", registration);
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
properties.getRegistration().getLogin().put("missing", login);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(
"Provider ID must be specified for client registration 'missing'");
@ -249,20 +285,20 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
@Test
public void oidcProviderConfigurationWhenProviderNotSpecifiedOnRegistration()
throws Exception {
Registration registration = new Registration();
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
testOidcConfiguration(registration, "okta");
LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setClientId("clientId");
login.setClientSecret("clientSecret");
testOidcConfiguration(login, "okta");
}
@Test
public void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration()
throws Exception {
Registration registration = new Registration();
registration.setProvider("okta-oidc");
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
testOidcConfiguration(registration, "okta-oidc");
OAuth2ClientProperties.LoginClientRegistration login = new LoginClientRegistration();
login.setProvider("okta-oidc");
login.setClientId("clientId");
login.setClientSecret("clientSecret");
testOidcConfiguration(login, "okta-oidc");
}
@Test
@ -273,13 +309,13 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
String issuer = this.server.url("").toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
Registration registration = new Registration();
registration.setProvider("okta-oidc");
registration.setClientId("clientId");
registration.setClientSecret("clientSecret");
registration.setClientAuthenticationMethod("post");
registration.setRedirectUriTemplate("http://example.com/redirect");
registration.setScope(Collections.singleton("user"));
OAuth2ClientProperties.LoginClientRegistration login = new OAuth2ClientProperties.LoginClientRegistration();
login.setProvider("okta-oidc");
login.setClientId("clientId");
login.setClientSecret("clientSecret");
login.setClientAuthenticationMethod("post");
login.setRedirectUriTemplate("http://example.com/redirect");
login.setScope(Collections.singleton("user"));
Provider provider = new Provider();
provider.setIssuerUri(issuer);
provider.setAuthorizationUri("http://example.com/auth");
@ -289,7 +325,7 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
provider.setJwkSetUri("http://example.com/jwk");
OAuth2ClientProperties properties = new OAuth2ClientProperties();
properties.getProvider().put("okta-oidc", provider);
properties.getRegistration().put("okta", registration);
properties.getRegistration().getLogin().put("okta", login);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("okta");
@ -313,8 +349,9 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
.isEqualTo("sub");
}
private void testOidcConfiguration(Registration registration, String providerId)
throws Exception {
private void testOidcConfiguration(
OAuth2ClientProperties.LoginClientRegistration registration,
String providerId) throws Exception {
this.server = new MockWebServer();
this.server.start();
String issuer = this.server.url("").toString();
@ -324,7 +361,7 @@ public class OAuth2ClientPropertiesRegistrationAdapterTests {
Provider provider = new Provider();
provider.setIssuerUri(issuer);
properties.getProvider().put(providerId, provider);
properties.getRegistration().put("okta", registration);
properties.getRegistration().getLogin().put("okta", registration);
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(properties);
ClientRegistration adapted = registrations.get("okta");

View File

@ -20,6 +20,9 @@ import org.junit.Rule;
import org.junit.Test;
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}.
*
@ -34,11 +37,11 @@ public class OAuth2ClientPropertiesTests {
public ExpectedException thrown = ExpectedException.none();
@Test
public void clientIdAbsentThrowsException() {
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
public void clientIdAbsentForLoginClientsThrowsException() {
LoginClientRegistration registration = new LoginClientRegistration();
registration.setClientSecret("secret");
registration.setProvider("google");
this.properties.getRegistration().put("foo", registration);
this.properties.getRegistration().getLogin().put("foo", registration);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Client id must not be empty.");
this.properties.validate();
@ -46,10 +49,30 @@ public class OAuth2ClientPropertiesTests {
@Test
public void clientSecretAbsentShouldNotThrowException() {
OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration();
LoginClientRegistration registration = new LoginClientRegistration();
registration.setClientId("foo");
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();
}

View File

@ -37,7 +37,7 @@ public class ReactiveOAuth2ClientAutoConfigurationTests {
.withConfiguration(
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
public void autoConfigurationShouldImportConfigurations() {

View File

@ -33,7 +33,7 @@ public class ReactiveOAuth2ClientRegistrationRepositoryConfigurationTests {
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
public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {

View File

@ -33,7 +33,7 @@ public class OAuth2ClientRegistrationRepositoryConfigurationTests {
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
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.InMemoryClientRegistrationRepository;
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.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -68,7 +68,28 @@ public class OAuth2WebSecurityConfigurationTests {
ClientRegistrationRepository expected = context
.getBean(ClientRegistrationRepository.class);
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");
assertThat(isEqual(expected.findByRegistrationId("first"),
actual.findByRegistrationId("first"))).isTrue();
@ -79,10 +100,14 @@ public class OAuth2WebSecurityConfigurationTests {
@Test
public void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() {
this.contextRunner
.withUserConfiguration(TestConfig.class,
OAuth2WebSecurityConfiguration.class)
.run((context) -> assertThat(getAuthCodeFilters(context)).isEmpty());
this.contextRunner.withUserConfiguration(TestConfig.class,
OAuth2WebSecurityConfiguration.class).run((context) -> {
assertThat(getFilters(context, OAuth2LoginAuthenticationFilter.class))
.isEmpty();
assertThat(
getFilters(context, OAuth2AuthorizationCodeGrantFilter.class))
.isEmpty();
});
}
@Test
@ -107,7 +132,11 @@ public class OAuth2WebSecurityConfigurationTests {
public void securityConfigurerBacksOffWhenOtherWebSecurityAdapterPresent() {
this.contextRunner.withUserConfiguration(TestWebSecurityConfigurerConfig.class,
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)
.isNotNull();
});
@ -137,19 +166,14 @@ public class OAuth2WebSecurityConfigurationTests {
}
@SuppressWarnings("unchecked")
private List<Filter> getAuthCodeFilters(AssertableApplicationContext context) {
private List<Filter> getFilters(AssertableApplicationContext context,
Class<? extends Filter> filter) {
FilterChainProxy filterChain = (FilterChainProxy) context
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
List<Filter> filters = (List<Filter>) ReflectionTestUtils
.getField(filterChains.get(0), "filters");
List<Filter> oauth2Filters = filters.stream()
.filter((f) -> f instanceof OAuth2LoginAuthenticationFilter
|| f instanceof OAuth2AuthorizationRequestRedirectFilter)
.collect(Collectors.toList());
return oauth2Filters.stream()
.filter((f) -> f instanceof OAuth2LoginAuthenticationFilter)
.collect(Collectors.toList());
return filters.stream().filter(filter::isInstance).collect(Collectors.toList());
}
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.
[[boot-features-security-oauth2-client]]
==== Client
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
makes use of the properties under `OAuth2ClientProperties`. The same properties are applicable
for both servlet and reactive applications.
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`.
You can register multiple OAuth2 clients and providers under the
`spring.security.oauth2.client` prefix, as shown in the following example:
You can register multiple OAuth2/OpenID Connect providers under the `spring.security.oauth2.client.provider`
prefix, as shown in the following example:
[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.token-uri=http://my-auth-server/oauth/token
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
----
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
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.
@ -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,
we provide a set of provider defaults (`google`, `github`, `facebook`, and `okta`,
respectively).
@ -3292,27 +3350,12 @@ In other words, the two configurations in the following example use the Google p
[source,properties,indent=0]
----
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.login.my-client.client-id=abcd
spring.security.oauth2.client.registration.login.my-client.client-secret=password
spring.security.oauth2.client.registration.login.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.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/
spring.security.oauth2.client.registration.login.google.client-id=abcd
spring.security.oauth2.client.registration.login.google.client-secret=password
----

View File

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

View File

@ -53,7 +53,7 @@ public class SampleOAuth2ClientApplicationTests {
}
@Test
public void loginShouldHaveBothOAuthClientsToChooseFrom() {
public void loginShouldHaveBothOAuth2LoginClientsToChooseFrom() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/login",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@ -62,4 +62,12 @@ public class SampleOAuth2ClientApplicationTests {
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:
client:
registration:
github-client-1:
client-id: ${APP-CLIENT-ID}
client-secret: ${APP-CLIENT-SECRET}
client-name: Github user
provider: github
scope: user
redirect-uri-template: http://localhost:8080/login/oauth2/code/github
github-client-2:
client-id: ${APP-CLIENT-ID}
client-secret: ${APP-CLIENT-SECRET}
client-name: Github email
provider: github
scope: user:email
redirect-uri-template: http://localhost:8080/login/oauth2/code/github
yahoo-oidc:
client-id: ${YAHOO-CLIENT-ID}
client-secret: ${YAHOO-CLIENT-SECRET}
login:
github-client-1:
client-id: ${APP-CLIENT-ID}
client-secret: ${APP-CLIENT-SECRET}
client-name: Github user
provider: github
scope: user
redirect-uri-template: http://localhost:8080/login/oauth2/code/github
github-client-2:
client-id: ${APP-CLIENT-ID}
client-secret: ${APP-CLIENT-SECRET}
client-name: Github email
provider: github
scope: user:email
redirect-uri-template: http://localhost:8080/login/oauth2/code/github
yahoo-oidc:
client-id: ${YAHOO-CLIENT-ID}
client-secret: ${YAHOO-CLIENT-SECRET}
provider:
yahoo-oidc:
issuer-uri: https://api.login.yahoo.com/