From dbe1d9608d8b251a086cc5a9f9c73c797726e70c Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 21 Sep 2017 15:47:53 -0700 Subject: [PATCH] Add auto-config for spring-security-oauth2-client Closes gh-10497 --- spring-boot-autoconfigure/pom.xml | 13 +- .../oauth2/client/AuthorizationGrantType.java | 45 +++ .../client/ClientAuthenticationMethod.java | 50 ++++ .../oauth2/client/CommonOAuth2Provider.java | 118 ++++++++ .../client/OAuth2ClientAutoConfiguration.java | 45 +++ .../oauth2/client/OAuth2ClientProperties.java | 259 ++++++++++++++++++ ...h2ClientPropertiesRegistrationAdapter.java | 115 ++++++++ ...ntRegistrationRepositoryConfiguration.java | 106 +++++++ .../OAuth2WebSecurityConfiguration.java | 61 +++++ .../main/resources/META-INF/spring.factories | 1 + .../client/AuthorizationGrantTypeTests.java | 36 +++ .../ClientAuthenticationMethodTests.java | 38 +++ .../client/CommonOAuth2ProviderTests.java | 144 ++++++++++ ...entPropertiesRegistrationAdapterTests.java | 169 ++++++++++++ .../client/OAuth2ClientPropertiesTests.java | 67 +++++ ...istrationRepositoryConfigurationTests.java | 58 ++++ .../OAuth2WebSecurityConfigurationTests.java | 161 +++++++++++ .../main/asciidoc/spring-boot-features.adoc | 52 ++++ spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-oauth2-client/pom.xml | 63 +++++ .../oauth2/client/ExampleController.java | 19 ++ .../client/SampleOAuth2ClientApplication.java | 16 ++ .../src/main/resources/application.yml | 19 ++ .../SampleOAuth2ClientApplicationTests.java | 51 ++++ 24 files changed, 1706 insertions(+), 1 deletion(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantType.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethod.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2Provider.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfiguration.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantTypeTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethodTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2ProviderTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfigurationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-oauth2-client/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/ExampleController.java create mode 100644 spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/SampleOAuth2ClientApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-oauth2-client/src/main/resources/application.yml create mode 100644 spring-boot-samples/spring-boot-sample-oauth2-client/src/test/main/java/sample/oauth2/client/SampleOAuth2ClientApplicationTests.java diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index a8ce696c036..20e8798836d 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.springframework.boot @@ -532,6 +533,16 @@ spring-security-webflux true + + org.springframework.security + spring-security-jwt-jose + true + + + org.springframework.security + spring-security-oauth2-client + true + org.springframework.session spring-session-core diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantType.java new file mode 100644 index 00000000000..d4c3c201580 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantType.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 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; + +/** + * OAuth 2.0 authorization grant types supported by Spring Boot. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.0.0 + */ +public enum AuthorizationGrantType { + + /** + * An {@code "authorization_code"} grant type. + */ + AUTHORIZATION_CODE( + org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE); + + private final org.springframework.security.oauth2.core.AuthorizationGrantType type; + + AuthorizationGrantType( + org.springframework.security.oauth2.core.AuthorizationGrantType type) { + this.type = type; + } + + org.springframework.security.oauth2.core.AuthorizationGrantType getType() { + return this.type; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethod.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethod.java new file mode 100644 index 00000000000..fb33b4881c9 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethod.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2017 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; + +/** + * OAuth 2.0 client authentication methods supported by Spring Boot. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.0.0 + * @see org.springframework.security.oauth2.core.ClientAuthenticationMethod + */ +public enum ClientAuthenticationMethod { + + /** + * HTTP BASIC client authentication. + */ + BASIC(org.springframework.security.oauth2.core.ClientAuthenticationMethod.BASIC), + + /** + * HTTP POST client authentication. + */ + POST(org.springframework.security.oauth2.core.ClientAuthenticationMethod.POST); + + private final org.springframework.security.oauth2.core.ClientAuthenticationMethod method; + + ClientAuthenticationMethod( + org.springframework.security.oauth2.core.ClientAuthenticationMethod method) { + this.method = method; + } + + org.springframework.security.oauth2.core.ClientAuthenticationMethod getMethod() { + return this.method; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2Provider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2Provider.java new file mode 100644 index 00000000000..b43cf40b26d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2Provider.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2017 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 org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; + +/** + * Common OAuth2 Providers that can be used to create + * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder + * builders} pre-configured with sensible defaults. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public enum CommonOAuth2Provider { + + GOOGLE { + + @Override + public Builder getBuilder(String registrationId) { + ClientRegistration.Builder builder = getBuilder(registrationId, + ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL); + builder.scope("openid", "profile", "email", "address", "phone"); + builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth"); + builder.tokenUri("https://www.googleapis.com/oauth2/v4/token"); + builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs"); + builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo"); + builder.clientName("Google"); + return builder; + } + + }, + + GITHUB { + + @Override + public Builder getBuilder(String registrationId) { + ClientRegistration.Builder builder = getBuilder(registrationId, + ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL); + builder.scope("user"); + builder.authorizationUri("https://github.com/login/oauth/authorize"); + builder.tokenUri("https://github.com/login/oauth/access_token"); + builder.userInfoUri("https://api.github.com/user"); + builder.userNameAttributeName("name"); + builder.clientName("GitHub"); + return builder; + } + + }, + + FACEBOOK { + + @Override + public Builder getBuilder(String registrationId) { + ClientRegistration.Builder builder = getBuilder(registrationId, + ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL); + builder.scope("public_profile", "email"); + builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth"); + builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token"); + builder.userInfoUri("https://graph.facebook.com/me"); + builder.userNameAttributeName("name"); + builder.clientName("Facebook"); + return builder; + } + + }, + + OKTA { + + @Override + public Builder getBuilder(String registrationId) { + ClientRegistration.Builder builder = getBuilder(registrationId, + ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL); + builder.scope("openid", "profile", "email", "address", "phone"); + builder.clientName("Okta"); + return builder; + } + + }; + + private static final String DEFAULT_REDIRECT_URL = "{scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}"; + + protected final ClientRegistration.Builder getBuilder(String registrationId, + ClientAuthenticationMethod method, String redirectUri) { + ClientRegistration.Builder builder = new ClientRegistration.Builder(registrationId); + builder.clientAuthenticationMethod(method); + builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); + builder.redirectUri(redirectUri); + return builder; + } + + /** + * Create a new + * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder + * ClientRegistration.Builder} pre-initialized with the provider settings. + * @param registrationId the registration-id used with the new builder + * @return a builder instance + */ + public abstract ClientRegistration.Builder getBuilder(String registrationId); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java new file mode 100644 index 00000000000..6747cab0402 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 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 org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.client.registration.ClientRegistration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OAuth client support. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.0.0 + */ +@Configuration +@AutoConfigureBefore(SecurityAutoConfiguration.class) +@ConditionalOnClass({ EnableWebSecurity.class, ClientRegistration.class }) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@Import({ OAuth2ClientRegistrationRepositoryConfiguration.class, + OAuth2WebSecurityConfiguration.class }) +public class OAuth2ClientAutoConfiguration { + +} + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientProperties.java new file mode 100644 index 00000000000..d9e13c08dbe --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientProperties.java @@ -0,0 +1,259 @@ +/* + * Copyright 2012-2017 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 java.util.Set; + +import javax.annotation.PostConstruct; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.StringUtils; + +/** + * OAuth 2.0 client properties. + * + * @author Madhura Bhave + * @author Phillip Webb + */ +@ConfigurationProperties(prefix = "spring.security.oauth2.client") +public class OAuth2ClientProperties { + + /** + * OAuth provider details. + */ + private Map provider = new HashMap<>(); + + /** + * Client registrations. + */ + private Map registration = new HashMap<>(); + + public Map getProvider() { + return this.provider; + } + + public Map getRegistration() { + return this.registration; + } + + @PostConstruct + public void validate() { + this.getRegistration().values().forEach(this::validateRegistration); + } + + private void validateRegistration(Registration registration) { + if (!StringUtils.hasText(registration.getClientId())) { + throw new IllegalStateException("Client id must not be empty."); + } + if (!StringUtils.hasText(registration.getClientSecret())) { + throw new IllegalStateException("Client secret must not be empty."); + } + if (!StringUtils.hasText(registration.getProvider())) { + throw new IllegalStateException("Provider must not be empty."); + } + } + + /** + * A single client registration. + */ + public static class Registration { + + /** + * 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, + * facebook, okta). + */ + private String provider; + + /** + * Client ID for the registration. + */ + private String clientId; + + /** + * Client secret of the registration. + */ + private String clientSecret; + + /** + * The client authentication method. May be left bank then using a pre-defined + * provider. + */ + private ClientAuthenticationMethod clientAuthenticationMethod; + + /** + * The authorization grant type. May be left bank then using a pre-defined + * provider. + */ + private AuthorizationGrantType authorizationGrantType; + + /** + * The redirect URI. May be left bank then using a pre-defined provider. + */ + private String redirectUri; + + /** + * The authorization scopes. May be left bank then using a pre-defined provider. + */ + private Set scope; + + /** + * The client name. May be left bank then using a pre-defined provider. + */ + private String clientName; + + public String getProvider() { + return this.provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return this.clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public ClientAuthenticationMethod getClientAuthenticationMethod() { + return this.clientAuthenticationMethod; + } + + public void setClientAuthenticationMethod( + ClientAuthenticationMethod clientAuthenticationMethod) { + this.clientAuthenticationMethod = clientAuthenticationMethod; + } + + public AuthorizationGrantType getAuthorizationGrantType() { + return this.authorizationGrantType; + } + + public void setAuthorizationGrantType( + AuthorizationGrantType authorizationGrantType) { + this.authorizationGrantType = authorizationGrantType; + } + + public String getRedirectUri() { + return this.redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public Set getScope() { + return this.scope; + } + + public void setScope(Set scope) { + this.scope = scope; + } + + public String getClientName() { + return this.clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + } + + public static class Provider { + + /** + * The authorization URI for the provider. + */ + private String authorizationUri; + + /** + * The token URI for the provider. + */ + private String tokenUri; + + /** + * The user info URI for the provider. + */ + private String userInfoUri; + + /** + * The name of the attribute that will be used to extract the username from the + * call to 'userInfoUri'. + */ + private String userNameAttribute; + + /** + * The JWK set URI for the provider. + */ + private String jwkSetUri; + + public String getAuthorizationUri() { + return this.authorizationUri; + } + + public void setAuthorizationUri(String authorizationUri) { + this.authorizationUri = authorizationUri; + } + + public String getTokenUri() { + return this.tokenUri; + } + + public void setTokenUri(String tokenUri) { + this.tokenUri = tokenUri; + } + + public String getUserInfoUri() { + return this.userInfoUri; + } + + public void setUserInfoUri(String userInfoUri) { + this.userInfoUri = userInfoUri; + } + + public String getUserNameAttribute() { + return this.userNameAttribute; + } + + public void setUserNameAttribute(String userNameAttribute) { + this.userNameAttribute = userNameAttribute; + } + + public String getJwkSetUri() { + return this.jwkSetUri; + } + + public void setJwkSetUri(String jwkSetUri) { + this.jwkSetUri = jwkSetUri; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java new file mode 100644 index 00000000000..1a812e9637e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2017 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 java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +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.bind.convert.BinderConversionService; +import org.springframework.core.convert.ConversionException; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; + +/** + * Adapter class to convert {@link OAuth2ClientProperties} to a + * {@link ClientRegistration}. + * + * @author Phillip Webb + * @since 2.0.0 + */ +final class OAuth2ClientPropertiesRegistrationAdapter { + + private OAuth2ClientPropertiesRegistrationAdapter() { + } + + public static Map getClientRegistrations( + OAuth2ClientProperties properties) { + Map clientRegistrations = new HashMap<>(); + properties.getRegistration().forEach((key, value) -> { + clientRegistrations.put(key, + getClientRegistration(key, value, properties.getProvider())); + }); + return clientRegistrations; + } + + private static ClientRegistration getClientRegistration(String registrationId, Registration properties, + Map providers) { + Builder builder = getBuilder(registrationId, properties.getProvider(), + providers); + copyIfNotNull(properties::getClientId, builder::clientId); + copyIfNotNull(properties::getClientSecret, builder::clientSecret); + copyIfNotNull(() -> properties.getClientAuthenticationMethod(), + builder::clientAuthenticationMethod, + ClientAuthenticationMethod::getMethod); + copyIfNotNull(() -> properties.getAuthorizationGrantType(), + builder::authorizationGrantType, AuthorizationGrantType::getType); + copyIfNotNull(properties::getRedirectUri, builder::redirectUri); + copyIfNotNull(properties::getScope, builder::scope, + (scope) -> scope.toArray(new String[scope.size()])); + copyIfNotNull(properties::getClientName, builder::clientName); + return builder.build(); + } + + private static Builder getBuilder(String registrationId, String providerId, + Map providers) { + CommonOAuth2Provider provider = getCommonProvider(providerId); + if (provider == null && !providers.containsKey(providerId)) { + throw new IllegalStateException("Unknown provider ID '" + providerId + "'"); + } + Builder builder = (provider != null ? provider.getBuilder(registrationId) : new Builder(registrationId)); + if (providers.containsKey(providerId)) { + return getBuilder(builder, providers.get(providerId)); + } + return builder; + } + + private static Builder getBuilder(Builder builder, Provider provider) { + copyIfNotNull(provider::getAuthorizationUri, builder::authorizationUri); + copyIfNotNull(provider::getTokenUri, builder::tokenUri); + copyIfNotNull(provider::getUserInfoUri, builder::userInfoUri); + copyIfNotNull(provider::getJwkSetUri, builder::jwkSetUri); + return builder; + } + + private static CommonOAuth2Provider getCommonProvider(String providerId) { + try { + return new BinderConversionService(null).convert(providerId, + CommonOAuth2Provider.class); + } + catch (ConversionException ex) { + return null; + } + } + + private static void copyIfNotNull(Supplier supplier, Consumer consumer) { + copyIfNotNull(supplier, consumer, Function.identity()); + } + + private static void copyIfNotNull(Supplier supplier, Consumer consumer, + Function converter) { + S value = supplier.get(); + if (value != null) { + consumer.accept(converter.apply(value)); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfiguration.java new file mode 100644 index 00000000000..4abe48d4a85 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfiguration.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2017 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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; + +/** + * {@link Configuration} used to map {@link OAuth2ClientProperties} to client + * registrations. + * + * @author Madhura Bhave + * @author Phillip Webb + */ +@Configuration +@EnableConfigurationProperties(OAuth2ClientProperties.class) +@Conditional(OAuth2ClientRegistrationRepositoryConfiguration.ClientsConfiguredCondition.class) +class OAuth2ClientRegistrationRepositoryConfiguration { + + private final OAuth2ClientProperties properties; + + OAuth2ClientRegistrationRepositoryConfiguration( + OAuth2ClientProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(ClientRegistrationRepository.class) + public InMemoryClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>( + OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(this.properties).values()); + return new InMemoryClientRegistrationRepository(registrations); + } + + /** + * Condition that matches if any {@code spring.security.oauth2.client.registration} + * properties are defined. + */ + static class ClientsConfiguredCondition extends SpringBootCondition { + + private static final Bindable> BINDABLE_REGISTRATION = Bindable + .mapOf(String.class, OAuth2ClientProperties.Registration.class); + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + ConditionMessage.Builder message = ConditionMessage + .forCondition("OAuth2 Clients Configured Condition"); + Map registrations = this + .getRegistrations(context.getEnvironment()); + if (!registrations.isEmpty()) { + return ConditionOutcome.match(message.foundExactly( + "registered clients " + registrations.values().stream() + .map(OAuth2ClientProperties.Registration::getClientId) + .collect(Collectors.joining(", ")))); + } + return ConditionOutcome.noMatch(message.notAvailable("registered clients")); + } + + private Map getRegistrations(Environment environment) { + return Binder.get(environment) + .bind("spring.security.oauth2.client.registration", + BINDABLE_REGISTRATION) + .orElse(Collections.emptyMap()); + } + + } + +} + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfiguration.java new file mode 100644 index 00000000000..3b4a1771a2f --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2017 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 org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; + +/** + * {@link WebSecurityConfigurerAdapter} to add OAuth client support. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.0.0 + */ +@Configuration +@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) +@ConditionalOnBean(ClientRegistrationRepository.class) +class OAuth2WebSecurityConfiguration { + + @Configuration + private static class OAuth2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + + private final ClientRegistrationRepository clientRegistrationRepository; + + OAuth2WebSecurityConfigurationAdapter( + ClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest() + .authenticated().and() + .oauth2Login() + .clients(this.clientRegistrationRepository); + } + + } + +} + diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 6d6a28959a6..8f9d4a390b4 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -102,6 +102,7 @@ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ +org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantTypeTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantTypeTests.java new file mode 100644 index 00000000000..7788d24005a --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/AuthorizationGrantTypeTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2017 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 org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AuthorizationGrantType}. + * + * @author Phillip Webb + */ +public class AuthorizationGrantTypeTests { + + @Test + public void getTypeShouldGetSpringSecurityVariant() throws Exception { + assertThat(AuthorizationGrantType.AUTHORIZATION_CODE.getType()).isEqualTo( + org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethodTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethodTests.java new file mode 100644 index 00000000000..3e8a62db27c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/ClientAuthenticationMethodTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2017 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 org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ClientAuthenticationMethod}. + * + * @author Phillip Webb + */ +public class ClientAuthenticationMethodTests { + + @Test + public void getMethodShouldGetSpringSecurityVariant() throws Exception { + assertThat(ClientAuthenticationMethod.BASIC.getMethod()).isEqualTo( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.BASIC); + assertThat(ClientAuthenticationMethod.POST.getMethod()).isEqualTo( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.POST); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2ProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2ProviderTests.java new file mode 100644 index 00000000000..9e3326c9799 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/CommonOAuth2ProviderTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2017 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 org.junit.Test; + +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; +import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CommonOAuth2Provider}. + * + * @author Phillip Webb + */ +public class CommonOAuth2ProviderTests { + + private static final String DEFAULT_REDIRECT_URL = "{scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}"; + + @Test + public void getBuilderWhenGoogleShouldHaveGoogleSettings() throws Exception { + ClientRegistration registration = build(CommonOAuth2Provider.GOOGLE); + ProviderDetails providerDetails = registration.getProviderDetails(); + assertThat(providerDetails.getAuthorizationUri()) + .isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); + assertThat(providerDetails.getTokenUri()) + .isEqualTo("https://www.googleapis.com/oauth2/v4/token"); + assertThat(providerDetails.getUserInfoEndpoint().getUri()) + .isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo"); + assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()) + .isEqualTo(null); + assertThat(providerDetails.getJwkSetUri()) + .isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); + assertThat(registration.getClientAuthenticationMethod()) + .isEqualTo(ClientAuthenticationMethod.BASIC); + assertThat(registration.getAuthorizationGrantType()) + .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); + assertThat(registration.getScope()).containsOnly("openid", "profile", "email", + "address", "phone"); + assertThat(registration.getClientName()).isEqualTo("Google"); + assertThat(registration.getRegistrationId()).isEqualTo("123"); + } + + @Test + public void getBuilderWhenGitHubShouldHaveGitHubSettings() throws Exception { + ClientRegistration registration = build(CommonOAuth2Provider.GITHUB); + ProviderDetails providerDetails = registration.getProviderDetails(); + assertThat(providerDetails.getAuthorizationUri()) + .isEqualTo("https://github.com/login/oauth/authorize"); + assertThat(providerDetails.getTokenUri()) + .isEqualTo("https://github.com/login/oauth/access_token"); + assertThat(providerDetails.getUserInfoEndpoint().getUri()) + .isEqualTo("https://api.github.com/user"); + assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()) + .isEqualTo("name"); + assertThat(providerDetails.getJwkSetUri()).isNull(); + assertThat(registration.getClientAuthenticationMethod()) + .isEqualTo(ClientAuthenticationMethod.BASIC); + assertThat(registration.getAuthorizationGrantType()) + .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); + assertThat(registration.getScope()).containsOnly("user"); + assertThat(registration.getClientName()).isEqualTo("GitHub"); + assertThat(registration.getRegistrationId()).isEqualTo("123"); + } + + @Test + public void getBuilderWhenFacebookShouldHaveFacebookSettings() throws Exception { + ClientRegistration registration = build(CommonOAuth2Provider.FACEBOOK); + ProviderDetails providerDetails = registration.getProviderDetails(); + assertThat(providerDetails.getAuthorizationUri()) + .isEqualTo("https://www.facebook.com/v2.8/dialog/oauth"); + assertThat(providerDetails.getTokenUri()) + .isEqualTo("https://graph.facebook.com/v2.8/oauth/access_token"); + assertThat(providerDetails.getUserInfoEndpoint().getUri()) + .isEqualTo("https://graph.facebook.com/me"); + assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()) + .isEqualTo("name"); + assertThat(providerDetails.getJwkSetUri()).isNull(); + assertThat(registration.getClientAuthenticationMethod()) + .isEqualTo(ClientAuthenticationMethod.POST); + assertThat(registration.getAuthorizationGrantType()) + .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); + assertThat(registration.getScope()).containsOnly("public_profile", "email"); + assertThat(registration.getClientName()).isEqualTo("Facebook"); + assertThat(registration.getRegistrationId()).isEqualTo("123"); + } + + @Test + public void getBuilderWhenOktaShouldHaveOktaSettings() throws Exception { + ClientRegistration registration = builder(CommonOAuth2Provider.OKTA) + .authorizationUri("http://example.com/auth") + .tokenUri("http://example.com/token") + .userInfoUri("http://example.com/info").build(); + ProviderDetails providerDetails = registration.getProviderDetails(); + assertThat(providerDetails.getAuthorizationUri()) + .isEqualTo("http://example.com/auth"); + assertThat(providerDetails.getTokenUri()).isEqualTo("http://example.com/token"); + assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("http://example.com/info"); + assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()) + .isEqualTo(null); + assertThat(providerDetails.getJwkSetUri()).isNull(); + assertThat(registration.getClientAuthenticationMethod()) + .isEqualTo(ClientAuthenticationMethod.BASIC); + assertThat(registration.getAuthorizationGrantType()) + .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); + assertThat(registration.getScope()).containsOnly("openid", "profile", "email", + "address", "phone"); + assertThat(registration.getClientName()).isEqualTo("Okta"); + assertThat(registration.getRegistrationId()).isEqualTo("123"); + } + + private ClientRegistration build(CommonOAuth2Provider provider) { + return builder(provider).build(); + } + + private Builder builder(CommonOAuth2Provider provider) { + return provider.getBuilder("123") + .clientId("abcd") + .clientSecret("secret"); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java new file mode 100644 index 00000000000..cd75a88cff0 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2012-2017 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.Map; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OAuth2ClientPropertiesRegistrationAdapter}. + * + * @author Phillip Webb + */ +public class OAuth2ClientPropertiesRegistrationAdapterTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getClientRegistrationsWhenUsingDefinedProviderShouldAdapt() + throws Exception { + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Provider provider = new Provider(); + provider.setAuthorizationUri("http://example.com/auth"); + provider.setTokenUri("http://example.com/token"); + provider.setUserInfoUri("http://example.com/info"); + provider.setJwkSetUri("http://example.com/jkw"); + Registration registration = new Registration(); + registration.setProvider("provider"); + registration.setClientId("clientId"); + registration.setClientSecret("clientSecret"); + registration.setClientAuthenticationMethod(ClientAuthenticationMethod.POST); + registration.setAuthorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); + registration.setRedirectUri("http://example.com/redirect"); + registration.setScope(Collections.singleton("scope")); + registration.setClientName("clientName"); + properties.getProvider().put("provider", provider); + properties.getRegistration().put("registration", registration); + Map registrations = OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(properties); + ClientRegistration adapted = registrations.get("registration"); + ProviderDetails adaptedProvider = adapted.getProviderDetails(); + assertThat(adaptedProvider.getAuthorizationUri()) + .isEqualTo("http://example.com/auth"); + assertThat(adaptedProvider.getTokenUri()).isEqualTo("http://example.com/token"); + assertThat(adaptedProvider.getUserInfoEndpoint().getUri()).isEqualTo("http://example.com/info"); + assertThat(adaptedProvider.getJwkSetUri()).isEqualTo("http://example.com/jkw"); + assertThat(adapted.getRegistrationId()).isEqualTo("registration"); + assertThat(adapted.getClientId()).isEqualTo("clientId"); + assertThat(adapted.getClientSecret()).isEqualTo("clientSecret"); + assertThat(adapted.getClientAuthenticationMethod()).isEqualTo( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.POST); + assertThat(adapted.getAuthorizationGrantType()).isEqualTo( + org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(adapted.getRedirectUri()).isEqualTo("http://example.com/redirect"); + assertThat(adapted.getScope()).containsExactly("scope"); + assertThat(adapted.getClientName()).isEqualTo("clientName"); + } + + @Test + public void getClientRegistrationsWhenUsingCommonProviderShouldAdapt() + throws Exception { + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Registration registration = new Registration(); + registration.setProvider("google"); + registration.setClientId("clientId"); + registration.setClientSecret("clientSecret"); + properties.getRegistration().put("registration", registration); + Map registrations = OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(properties); + ClientRegistration adapted = registrations.get("registration"); + 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.getJwkSetUri()) + .isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); + assertThat(adapted.getRegistrationId()).isEqualTo("registration"); + assertThat(adapted.getClientId()).isEqualTo("clientId"); + assertThat(adapted.getClientSecret()).isEqualTo("clientSecret"); + 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.getRedirectUri()).isEqualTo( + "{scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}"); + assertThat(adapted.getScope()).containsExactly("openid", "profile", "email", + "address", "phone"); + assertThat(adapted.getClientName()).isEqualTo("Google"); + } + + @Test + public void getClientRegistrationsWhenUsingCommonProviderWithOverrideShouldAdapt() + throws Exception { + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Registration registration = new Registration(); + registration.setProvider("google"); + registration.setClientId("clientId"); + registration.setClientSecret("clientSecret"); + registration.setClientAuthenticationMethod(ClientAuthenticationMethod.POST); + registration.setAuthorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); + registration.setRedirectUri("http://example.com/redirect"); + registration.setScope(Collections.singleton("scope")); + registration.setClientName("clientName"); + properties.getRegistration().put("registration", registration); + Map registrations = OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(properties); + ClientRegistration adapted = registrations.get("registration"); + 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.getJwkSetUri()) + .isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); + assertThat(adapted.getRegistrationId()).isEqualTo("registration"); + assertThat(adapted.getClientId()).isEqualTo("clientId"); + assertThat(adapted.getClientSecret()).isEqualTo("clientSecret"); + assertThat(adapted.getClientAuthenticationMethod()).isEqualTo( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.POST); + assertThat(adapted.getAuthorizationGrantType()).isEqualTo( + org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(adapted.getRedirectUri()).isEqualTo("http://example.com/redirect"); + assertThat(adapted.getScope()).containsExactly("scope"); + assertThat(adapted.getClientName()).isEqualTo("clientName"); + } + + @Test + public void getClientRegistrationsWhenUnknownProviderShouldThrowException() + throws Exception { + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Registration registration = new Registration(); + registration.setProvider("missing"); + properties.getRegistration().put("registration", registration); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Unknown provider ID 'missing'"); + OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesTests.java new file mode 100644 index 00000000000..ec95faadfdb --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2017 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 org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * Tests for {@link OAuth2ClientProperties}. + * + * @author Madhura Bhave + */ +public class OAuth2ClientPropertiesTests { + + private OAuth2ClientProperties properties = new OAuth2ClientProperties(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void clientIdAbsentThrowsException() throws Exception { + OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); + registration.setClientSecret("secret"); + registration.setProvider("google"); + this.properties.getRegistration().put("foo", registration); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Client id must not be empty."); + this.properties.validate(); + } + + @Test + public void clientSecretAbsentThrowsException() throws Exception { + OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); + registration.setClientId("foo"); + registration.setProvider("google"); + this.properties.getRegistration().put("foo", registration); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Client secret must not be empty."); + this.properties.validate(); + } + + @Test + public void providerAbsentThrowsException() throws Exception { + OAuth2ClientProperties.Registration registration = new OAuth2ClientProperties.Registration(); + registration.setClientId("foo"); + registration.setClientSecret("secret"); + this.properties.getRegistration().put("foo", registration); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Provider must not be empty."); + this.properties.validate(); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfigurationTests.java new file mode 100644 index 00000000000..c2d4bd1fcb6 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientRegistrationRepositoryConfigurationTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2017 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 org.junit.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OAuth2ClientRegistrationRepositoryConfiguration}. + * + * @author Madhura Bhave + */ +public class OAuth2ClientRegistrationRepositoryConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; + + @Test + public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() throws Exception { + this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class) + .run(context -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class)); + } + + @Test + public void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() throws Exception { + this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class) + .withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd", + REGISTRATION_PREFIX + ".foo.client-secret=secret", + REGISTRATION_PREFIX + ".foo.provider=github") + .run(context -> { + ClientRegistrationRepository repository = context.getBean(ClientRegistrationRepository.class); + ClientRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration).isNotNull(); + assertThat(registration.getClientSecret()).isEqualTo("secret"); + }); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfigurationTests.java new file mode 100644 index 00000000000..4ae5fdaefb6 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2WebSecurityConfigurationTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2017 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.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.Filter; + +import org.junit.Test; + +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationProcessingFilter; +import org.springframework.security.oauth2.client.web.AuthorizationCodeRequestRedirectFilter; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.ObjectUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OAuth2WebSecurityConfiguration}. + * + * @author Madhura Bhave + */ +public class OAuth2WebSecurityConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + @SuppressWarnings("unchecked") + public void securityConfigurerRegistersClientRegistrations() throws Exception { + this.contextRunner.withUserConfiguration( + ClientRepositoryConfiguration.class, OAuth2WebSecurityConfiguration.class) + .run(context -> { + ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); + ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( + getAuthCodeFilters(context).get(0), "clientRegistrationRepository"); + assertThat(isEqual(expected.findByRegistrationId("first"), + actual.findByRegistrationId("first"))).isTrue(); + assertThat(isEqual(expected.findByRegistrationId("second"), + actual.findByRegistrationId("second"))).isTrue(); + }); + } + + @Test + public void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() throws Exception { + this.contextRunner.withUserConfiguration(TestConfig.class, OAuth2WebSecurityConfiguration.class) + .run(context -> assertThat(getAuthCodeFilters(context)).isEmpty()); + } + + @Test + public void securityConfigurerBacksOffWhenOtherWebSecurityAdapterPresent() throws Exception { + this.contextRunner.withUserConfiguration(TestWebSecurityConfigurerConfig.class, OAuth2WebSecurityConfiguration.class) + .run(context -> assertThat(getAuthCodeFilters(context)).isEmpty()); + } + + @SuppressWarnings("unchecked") + private List getAuthCodeFilters(AssertableApplicationContext context) { + FilterChainProxy filterChain = (FilterChainProxy) context.getBean("springSecurityFilterChain"); + List filterChains = filterChain.getFilterChains(); + List filters = (List) ReflectionTestUtils.getField(((List) filterChains).get(0), "filters"); + List oauth2Filters = filters.stream().filter( + f -> f instanceof AuthorizationCodeAuthenticationProcessingFilter || + f instanceof AuthorizationCodeRequestRedirectFilter).collect(Collectors.toList()); + return oauth2Filters.stream().filter(f -> f instanceof AuthorizationCodeAuthenticationProcessingFilter) + .collect(Collectors.toList()); + } + + private boolean isEqual(ClientRegistration reg1, ClientRegistration reg2) { + boolean result = ObjectUtils.nullSafeEquals(reg1.getClientId(), reg2.getClientId()); + result = result && ObjectUtils.nullSafeEquals(reg1.getClientName(), reg2.getClientName()); + result = result && ObjectUtils.nullSafeEquals(reg1.getClientSecret(), reg2.getClientSecret()); + result = result && ObjectUtils.nullSafeEquals(reg1.getScope(), reg2.getScope()); + result = result && ObjectUtils.nullSafeEquals(reg1.getRedirectUri(), reg2.getRedirectUri()); + result = result && ObjectUtils.nullSafeEquals(reg1.getRegistrationId(), reg2.getRegistrationId()); + result = result && ObjectUtils.nullSafeEquals(reg1.getAuthorizationGrantType(), reg2.getAuthorizationGrantType()); + result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getAuthorizationUri(), + reg2.getProviderDetails().getAuthorizationUri()); + result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getUserInfoEndpoint(), + reg2.getProviderDetails().getUserInfoEndpoint()); + result = result && ObjectUtils.nullSafeEquals(reg1.getProviderDetails().getTokenUri(), + reg2.getProviderDetails().getTokenUri()); + return result; + } + + @Configuration + @EnableWebSecurity + protected static class TestConfig { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + } + + @Configuration + @Import(TestConfig.class) + static class ClientRepositoryConfiguration { + + @Bean + public ClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>(); + registrations.add(getClientRegistration("first", "http://user-info-uri.com")); + registrations.add(getClientRegistration("second", "http://other-user-info")); + return new InMemoryClientRegistrationRepository(registrations); + } + + private ClientRegistration getClientRegistration(String id, String userInfoUri) { + ClientRegistration.Builder builder = new ClientRegistration.Builder(id); + builder.clientName("foo") + .clientId("foo") + .clientAuthenticationMethod(org.springframework.security.oauth2.core.ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read") + .clientSecret("secret") + .redirectUri("http://redirect-uri.com") + .authorizationUri("http://authorization-uri.com") + .tokenUri("http://token-uri.com") + .userInfoUri(userInfoUri) + .userNameAttributeName("login"); + return builder.build(); + } + + } + + @Configuration + @Import({ ClientRepositoryConfiguration.class }) + static class TestWebSecurityConfigurerConfig extends WebSecurityConfigurerAdapter { + + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 403de8eb0d4..2acdc0e3134 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2750,6 +2750,58 @@ NOTE: By default, a `WebSecurityConfigurerAdapter` will match any path. If you d to completely override Spring Boot's auto-configured access rules, your adapter must explicitly configure the paths that you do want to override. +[[boot-features-security-oauth2]] +=== OAuth2 + +=== 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`. +You can register multiple OAuth2 clients and providers under the `spring.security.oauth2.client` prefix. +For example, + +[source,yaml,indent=0] +---- +# application.yml + +spring: + security: + oauth2: + client: + registration: + my-client-1: + client-id: abcd + client-secret: password + client-name: Client for user scope + provider: my-oauth-provider + scope: user + redirect-uri: http://my-redirect-uri.com + authentication-method: basic + authorization-grant-type: authorization_code + my-client2: + client-id: abcd + client-secret: password + client-name: Client for email scope + provider: my-oauth-provider + scope: email + redirect-uri: http://my-redirect-uri.com + authentication-method: basic + authorization-grant-type: authorization_code + provider: + my-oauth-provider: + authorization-uri: http://my-auth-server/oauth/authorize + token-uri: http://my-auth-server/oauth/token + user-info-uri: http://my-auth-server/userinfo + jwk-set-uri: http://my-auth-server/token_keys + user-name-attribute: name + +# additional configuration as required +---- + +NOTE: For common OAuth2 and OpenID providers such as Google, Github, Facebook and Okta, we provide a set of +provider defaults. If you don't need to customize these providers, you do not need to provide the `provider` +configuration. The client registration `provider` key should reference one these providers. [[boot-features-security-actuator]] diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index a6c1e530d8c..edd2a38516f 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -62,6 +62,7 @@ spring-boot-sample-junit-jupiter spring-boot-sample-liquibase spring-boot-sample-logback + spring-boot-sample-oauth2-client spring-boot-sample-parent-context spring-boot-sample-profile spring-boot-sample-property-validation diff --git a/spring-boot-samples/spring-boot-sample-oauth2-client/pom.xml b/spring-boot-samples/spring-boot-sample-oauth2-client/pom.xml new file mode 100644 index 00000000000..69c82be0b19 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-client/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 2.0.0.BUILD-SNAPSHOT + + spring-boot-sample-oauth2-client + Spring Boot Sample OAuth2 Client + Spring Boot Sample OAuth2 Client + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-oauth2-client + + + org.springframework.security + spring-security-jwt-jose + + + + org.apache.httpcomponents + httpclient + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/ExampleController.java b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/ExampleController.java new file mode 100644 index 00000000000..4b31edeb4fc --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/ExampleController.java @@ -0,0 +1,19 @@ +package sample.oauth2.client; + +import java.security.Principal; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Madhura Bhave + */ +@RestController +public class ExampleController { + + @RequestMapping("/") + public String email(Principal principal) { + return "Hello " + principal.getName(); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/SampleOAuth2ClientApplication.java b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/SampleOAuth2ClientApplication.java new file mode 100644 index 00000000000..5d6cd8059c0 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/java/sample/oauth2/client/SampleOAuth2ClientApplication.java @@ -0,0 +1,16 @@ +package sample.oauth2.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Madhura Bhave + */ +@SpringBootApplication +public class SampleOAuth2ClientApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleOAuth2ClientApplication.class); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/resources/application.yml b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/resources/application.yml new file mode 100644 index 00000000000..5ee1883be1d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-client/src/main/resources/application.yml @@ -0,0 +1,19 @@ +spring: + security: + oauth2: + client: + registration: + github-client-1: + client-id: 1b4c6c2d23a2dbec8959 + client-secret: 8bdb4047d98f37e1c6aeba9ffd2017bc31682e1b + client-name: Github user + provider: github + scope: user + redirect_uri: http://localhost:8080/oauth2/authorize/code/github + github-client-2: + client-id: 1b4c6c2d23a2dbec8959 + client-secret: 8bdb4047d98f37e1c6aeba9ffd2017bc31682e1b + client-name: Github email + provider: github + scope: user:email + redirect_uri: http://localhost:8080/oauth2/authorize/code/github \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-oauth2-client/src/test/main/java/sample/oauth2/client/SampleOAuth2ClientApplicationTests.java b/spring-boot-samples/spring-boot-sample-oauth2-client/src/test/main/java/sample/oauth2/client/SampleOAuth2ClientApplicationTests.java new file mode 100644 index 00000000000..62a3bc8af2a --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-client/src/test/main/java/sample/oauth2/client/SampleOAuth2ClientApplicationTests.java @@ -0,0 +1,51 @@ +package sample.oauth2.client; + +import java.net.URI; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for an OAuth2 client application. + * + * @author Madhura Bhave + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext +public class SampleOAuth2ClientApplicationTests { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void everythingShouldRedirectToLogin() throws Exception { + ResponseEntity entity = this.restTemplate.getForEntity("/", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(entity.getHeaders().getLocation()) + .isEqualTo(URI.create("http://localhost:" + this.port + "/login")); + } + + @Test + public void loginShouldHaveBothOAuthClientsToChooseFrom() throws Exception { + ResponseEntity entity = this.restTemplate.getForEntity("/login", String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("/oauth2/authorization/code/github-client-1"); + assertThat(entity.getBody()).contains("/oauth2/authorization/code/github-client-2"); + } + +} \ No newline at end of file