Add auto-config for WebFlux OAuth2 Login

Closes gh-13142
This commit is contained in:
Madhura Bhave 2018-05-30 17:58:55 -07:00
parent 792f0b190d
commit 9f4a5c13a5
24 changed files with 751 additions and 58 deletions

View File

@ -0,0 +1,63 @@
/*
* 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.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.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Condition that matches if any {@code spring.security.oauth2.client.registration}
* properties are defined.
*/
public class ClientsConfiguredCondition extends SpringBootCondition {
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> 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<String, OAuth2ClientProperties.Registration> 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<String, OAuth2ClientProperties.Registration> getRegistrations(
Environment environment) {
return Binder.get(environment)
.bind("spring.security.oauth2.client.registration", BINDABLE_REGISTRATION)
.orElse(Collections.emptyMap());
}
}

View File

@ -39,7 +39,7 @@ import org.springframework.util.StringUtils;
* @author Thiago Hirata * @author Thiago Hirata
* @since 2.0.0 * @since 2.0.0
*/ */
final class OAuth2ClientPropertiesRegistrationAdapter { public final class OAuth2ClientPropertiesRegistrationAdapter {
private OAuth2ClientPropertiesRegistrationAdapter() { private OAuth2ClientPropertiesRegistrationAdapter() {
} }

View File

@ -15,6 +15,6 @@
*/ */
/** /**
* Auto-configuration for Spring Security's OAuth 2 client. * Support for Spring Security's OAuth 2 client.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client;

View File

@ -0,0 +1,43 @@
/*
* 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.reactive;
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.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security's Reactive
* OAuth2 client.
*
* @author Madhura Bhave
* @since 2.1.0
*/
@Configuration
@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
@ConditionalOnClass({ EnableWebFluxSecurity.class, ClientRegistration.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Import({ ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class,
ReactiveOAuth2WebSecurityConfiguration.class })
public class ReactiveOAuth2ClientAutoConfiguration {
}

View File

@ -0,0 +1,60 @@
/*
* 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.reactive;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
/**
* {@link Configuration} used to map {@link OAuth2ClientProperties} to client
* registrations.
*
* @author Madhura Bhave
*/
@Configuration
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ClientsConfiguredCondition.class)
class ReactiveOAuth2ClientRegistrationRepositoryConfiguration {
private final OAuth2ClientProperties properties;
ReactiveOAuth2ClientRegistrationRepositoryConfiguration(
OAuth2ClientProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class)
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter
.getClientRegistrations(this.properties).values());
return new InMemoryReactiveClientRegistrationRepository(registrations);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.reactive;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
/**
* {@link Configuration} used to create an in-memory
* {@link ReactiveOAuth2AuthorizedClientService}.
*
* @author Madhura Bhave
*/
@Configuration
public class ReactiveOAuth2WebSecurityConfiguration {
@Bean
@ConditionalOnBean(ReactiveClientRegistrationRepository.class)
@ConditionalOnMissingBean
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryReactiveOAuth2AuthorizedClientService(
clientRegistrationRepository);
}
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for Spring Security's Reactive OAuth 2 client.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client.reactive;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

View File

@ -14,28 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; 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.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; 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.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
@ -45,11 +36,10 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
* registrations. * registrations.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(OAuth2ClientProperties.class) @EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(OAuth2ClientRegistrationRepositoryConfiguration.ClientsConfiguredCondition.class) @Conditional(ClientsConfiguredCondition.class)
class OAuth2ClientRegistrationRepositoryConfiguration { class OAuth2ClientRegistrationRepositoryConfiguration {
private final OAuth2ClientProperties properties; private final OAuth2ClientProperties properties;
@ -67,38 +57,4 @@ class OAuth2ClientRegistrationRepositoryConfiguration {
return new InMemoryClientRegistrationRepository(registrations); 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<Map<String, Registration>> 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<String, Registration> 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<String, Registration> getRegistrations(Environment environment) {
return Binder.get(environment)
.bind("spring.security.oauth2.client.registration",
BINDABLE_REGISTRATION)
.orElse(Collections.emptyMap());
}
}
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for Spring Security's OAuth 2 client.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;

View File

@ -103,7 +103,8 @@ org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoCon
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\

View File

@ -0,0 +1,101 @@
/*
* 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.reactive;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactiveOAuth2ClientAutoConfiguration}.
*
* @author Madhura Bhave
*/
public class ReactiveOAuth2ClientAutoConfigurationTests {
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class));
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
@Test
public void autoConfigurationShouldImportConfigurations() {
this.contextRunner.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret",
REGISTRATION_PREFIX + ".foo.provider=github").run((context) -> {
assertThat(context).hasSingleBean(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class);
assertThat(context)
.hasSingleBean(ReactiveOAuth2WebSecurityConfiguration.class);
});
}
@Test
public void autoConfigurationConditionalOnClassEnableWebFluxSecurity() {
FilteredClassLoader classLoader = new FilteredClassLoader(
EnableWebFluxSecurity.class);
this.contextRunner.withClassLoader(classLoader)
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret",
REGISTRATION_PREFIX + ".foo.provider=github")
.run((context) -> {
assertThat(context).doesNotHaveBean(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class);
assertThat(context).doesNotHaveBean(
ReactiveOAuth2WebSecurityConfiguration.class);
});
}
@Test
public void autoConfigurationConditionalOnClassClientRegistration() {
FilteredClassLoader classLoader = new FilteredClassLoader(
ClientRegistration.class);
this.contextRunner.withClassLoader(classLoader)
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret",
REGISTRATION_PREFIX + ".foo.provider=github")
.run((context) -> {
assertThat(context).doesNotHaveBean(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class);
assertThat(context).doesNotHaveBean(
ReactiveOAuth2WebSecurityConfiguration.class);
});
}
@Test
public void autoConfigurationConditionalOnReactiveWebApplication() {
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations
.of(ReactiveOAuth2ClientAutoConfiguration.class));
contextRunner.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret",
REGISTRATION_PREFIX + ".foo.provider=github").run((context) -> {
assertThat(context).doesNotHaveBean(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class);
assertThat(context).doesNotHaveBean(
ReactiveOAuth2WebSecurityConfiguration.class);
});
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.reactive;
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 org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactiveOAuth2ClientRegistrationRepositoryConfiguration}.
*
* @author Madhura Bhave
*/
public class ReactiveOAuth2ClientRegistrationRepositoryConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration";
@Test
public void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() {
this.contextRunner
.withUserConfiguration(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class)
.run((context) -> assertThat(context)
.doesNotHaveBean(ClientRegistrationRepository.class));
}
@Test
public void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() {
this.contextRunner
.withUserConfiguration(
ReactiveOAuth2ClientRegistrationRepositoryConfiguration.class)
.withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd",
REGISTRATION_PREFIX + ".foo.client-secret=secret",
REGISTRATION_PREFIX + ".foo.provider=github")
.run((context) -> {
ReactiveClientRegistrationRepository repository = context
.getBean(ReactiveClientRegistrationRepository.class);
ClientRegistration registration = repository
.findByRegistrationId("foo").block();
assertThat(registration).isNotNull();
assertThat(registration.getClientSecret()).isEqualTo("secret");
});
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.reactive;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReactiveOAuth2WebSecurityConfiguration}.
*
* @author Madhura Bhave
*/
public class ReactiveOAuth2WebSecurityConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
public void authorizedClientServiceBeanIsConditionalOnClientRegistrationRepository() {
this.contextRunner
.withUserConfiguration(ReactiveOAuth2WebSecurityConfiguration.class)
.run((context) -> assertThat(context)
.doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class));
}
@Test
public void configurationRegistersAuthorizedClientServiceBean() {
this.contextRunner
.withUserConfiguration(ReactiveClientRepositoryConfiguration.class,
ReactiveOAuth2WebSecurityConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(ReactiveOAuth2AuthorizedClientService.class));
}
@Test
public void authorizedClientServiceBeanIsConditionalOnMissingBean() {
this.contextRunner
.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class,
ReactiveOAuth2WebSecurityConfiguration.class)
.run((context) -> {
assertThat(context)
.hasSingleBean(ReactiveOAuth2AuthorizedClientService.class);
assertThat(context).hasBean("testAuthorizedClientService");
});
}
@Configuration
static class ReactiveClientRepositoryConfiguration {
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = new ArrayList<>();
registrations.add(getClientRegistration("first", "http://user-info-uri.com"));
registrations.add(getClientRegistration("second", "http://other-user-info"));
return new InMemoryReactiveClientRegistrationRepository(registrations);
}
private ClientRegistration getClientRegistration(String id, String userInfoUri) {
ClientRegistration.Builder builder = ClientRegistration
.withRegistrationId(id);
builder.clientName("foo").clientId("foo").clientAuthenticationMethod(
org.springframework.security.oauth2.core.ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("read").clientSecret("secret")
.redirectUriTemplate("http://redirect-uri.com")
.authorizationUri("http://authorization-uri.com")
.tokenUri("http://token-uri.com").userInfoUri(userInfoUri)
.userNameAttributeName("login");
return builder.build();
}
}
@Configuration
@Import(ReactiveClientRepositoryConfiguration.class)
static class OAuth2AuthorizedClientServiceConfiguration {
@Bean
public ReactiveOAuth2AuthorizedClientService testAuthorizedClientService(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryReactiveOAuth2AuthorizedClientService(
clientRegistrationRepository);
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import org.junit.Test; import org.junit.Test;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.client; package org.springframework.boot.autoconfigure.security.oauth2.client.servlet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -3151,7 +3151,8 @@ Spring.
==== Client ==== Client
If you have `spring-security-oauth2-client` on your classpath, you can take advantage of If you have `spring-security-oauth2-client` on your classpath, you can take advantage of
some auto-configuration to make it easy to set up an OAuth2 Client. This configuration some auto-configuration to make it easy to set up an OAuth2 Client. This configuration
makes use of the properties under `OAuth2ClientProperties`. makes use of the properties under `OAuth2ClientProperties`. The same properties are applicable
for both servlet and reactive applications.
You can register multiple OAuth2 clients and providers under the You can register multiple OAuth2 clients and providers under the
`spring.security.oauth2.client` prefix, as shown in the following example: `spring.security.oauth2.client` prefix, as shown in the following example:
@ -3186,7 +3187,7 @@ You can register multiple OAuth2 clients and providers under the
By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs By default, Spring Security's `OAuth2LoginAuthenticationFilter` only processes URLs
matching `/login/oauth2/code/*`. If you want to customize the `redirect-uri-template` to matching `/login/oauth2/code/*`. If you want to customize the `redirect-uri-template` to
use a different pattern, you need to provide configuration to process that custom pattern. use a different pattern, you need to provide configuration to process that custom pattern.
For example, you can add your own `WebSecurityConfigurerAdapter` that resembles the For example, for servlet applications, you can add your own `WebSecurityConfigurerAdapter` that resembles the
following: following:
[source,java,indent=0] [source,java,indent=0]

View File

@ -0,0 +1,12 @@
= Spring Boot Sample Reactive OAuth2 Client
== Register Github OAuth2 application
To run the sample, you need to link:https://github.com/settings/applications/new[register an OAuth application on Github].
While registering your application, ensure the Authorization callback URL is set to http://localhost:8080/login/oauth2/code/github.
After completing the registration, you will have a new OAuth Application with a Client ID and Client Secret.
== Configuring application.yml
Once the OAuth application is registered with GitHub, you need to configure the sample application to use this OAuth application (client).
Edit the link:src/main/resources/application.yml[application.yml] and replace ${APP-CLIENT-ID} and ${APP-CLIENT-SECRET} with the OAuth client credentials created in the previous section.
The sample can now be run and you can login with your Github user credentials.

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>${revision}</version>
</parent>
<artifactId>spring-boot-sample-reactive-oauth2-client</artifactId>
<name>Spring Boot Sample Reactive OAuth2 Client</name>
<description>Spring Boot Sample Reactive OAuth2 Client</description>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,32 @@
/*
* 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 sample.oauth2.client;
import java.security.Principal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@RequestMapping("/")
public String email(Principal principal) {
return "Hello " + principal.getName();
}
}

View File

@ -0,0 +1,29 @@
/*
* 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 sample.oauth2.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleReactiveOAuth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SampleReactiveOAuth2ClientApplication.class);
}
}

View File

@ -0,0 +1,19 @@
spring:
security:
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

View File

@ -0,0 +1,54 @@
/*
* 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 sample.oauth2.client;
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.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
"APP-CLIENT-ID=my-client-id", "APP-CLIENT-SECRET=my-client-secret" })
public class SampleReactiveOAuth2ClientApplicationTests {
@Autowired
private WebTestClient webTestClient;
@Test
public void everythingShouldRedirectToLogin() {
this.webTestClient.get().uri("/").exchange()
.expectStatus().isFound()
.expectHeader().valueEquals("Location", "/login");
}
@Test
public void loginShouldHaveBothOAuthClientsToChooseFrom() {
byte[] body = this.webTestClient.get().uri("/login").exchange()
.expectStatus().isOk()
.returnResult(String.class).getResponseBodyContent();
String bodyString = new String(body);
assertThat(bodyString).contains("/oauth2/authorization/github-client-1");
assertThat(bodyString).contains("/oauth2/authorization/github-client-2");
}
}