Skip to content

Commit 25268c0

Browse files
committed
Add auto-configuration for spring-security-oauth2-client
This commit provides an auto-configuration for Spring Security 5.0 OAuth 2.0 / OpenID Connect 1.0 client support. The OAuth2ClientAutoConfiguration will register a ClientRegistrationRepository with the context and will enable httpSecurity.oauth2Login() based on a number of conditions.
1 parent a4a19e1 commit 25268c0

File tree

8 files changed

+1016
-1
lines changed

8 files changed

+1016
-1
lines changed

spring-boot-autoconfigure/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,11 @@
516516
<artifactId>spring-security-data</artifactId>
517517
<optional>true</optional>
518518
</dependency>
519+
<dependency>
520+
<groupId>org.springframework.security</groupId>
521+
<artifactId>spring-security-oauth2-client</artifactId>
522+
<optional>true</optional>
523+
</dependency>
519524
<dependency>
520525
<groupId>org.springframework.security.oauth</groupId>
521526
<artifactId>spring-security-oauth2</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security.oauth2.client;
18+
19+
import java.io.IOException;
20+
21+
import org.springframework.boot.SpringApplication;
22+
import org.springframework.boot.env.EnvironmentPostProcessor;
23+
import org.springframework.core.Ordered;
24+
import org.springframework.core.env.ConfigurableEnvironment;
25+
import org.springframework.core.io.DefaultResourceLoader;
26+
import org.springframework.core.io.ResourceLoader;
27+
import org.springframework.core.io.support.ResourcePropertySource;
28+
29+
/**
30+
* An {@link EnvironmentPostProcessor} that registers a <code>PropertySource</code> against the
31+
* <code>Environment</code>, which contains a default set of client properties.
32+
*
33+
* @author Joe Grandja
34+
* @since 2.0.0
35+
*/
36+
public class ClientPropertyDefaultsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
37+
38+
public static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
39+
40+
static final String CLIENT_DEFAULTS_RESOURCE = "META-INF/spring-security-oauth2-client-defaults.properties";
41+
42+
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
43+
44+
private int order = DEFAULT_ORDER;
45+
46+
@Override
47+
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
48+
if (this.isClientsConfigured(environment)) {
49+
try {
50+
environment.getPropertySources().addLast(
51+
new ResourcePropertySource(this.resourceLoader.getResource(CLIENT_DEFAULTS_RESOURCE)));
52+
}
53+
catch (IOException ex) {
54+
throw new IllegalStateException("Failed to load class path resource: " + CLIENT_DEFAULTS_RESOURCE, ex);
55+
}
56+
}
57+
}
58+
59+
@Override
60+
public int getOrder() {
61+
return this.order;
62+
}
63+
64+
public void setOrder(int order) {
65+
this.order = order;
66+
}
67+
68+
private boolean isClientsConfigured(ConfigurableEnvironment environment) {
69+
return !OAuth2ClientAutoConfiguration.getClientKeys(environment).isEmpty();
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.security.oauth2.client;
18+
19+
import java.net.URI;
20+
import java.util.AbstractMap;
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
28+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
29+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
31+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
37+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
38+
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
39+
import org.springframework.boot.context.properties.bind.Bindable;
40+
import org.springframework.boot.context.properties.bind.Binder;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.ConditionContext;
43+
import org.springframework.context.annotation.Conditional;
44+
import org.springframework.context.annotation.Configuration;
45+
import org.springframework.context.annotation.ConfigurationCondition;
46+
import org.springframework.core.annotation.Order;
47+
import org.springframework.core.env.Environment;
48+
import org.springframework.core.type.AnnotatedTypeMetadata;
49+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
50+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
51+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
52+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
53+
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
54+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
55+
import org.springframework.security.oauth2.client.registration.ClientRegistrationProperties;
56+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
57+
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
58+
59+
/**
60+
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security OAuth 2.0 / OpenID Connect 1.0 client support.
61+
*
62+
* @author Joe Grandja
63+
* @since 2.0.0
64+
*/
65+
@Configuration
66+
@ConditionalOnWebApplication(type = Type.SERVLET)
67+
@ConditionalOnClass({EnableWebSecurity.class, ClientRegistration.class})
68+
@AutoConfigureBefore(SecurityAutoConfiguration.class)
69+
public class OAuth2ClientAutoConfiguration {
70+
71+
private static final String CLIENT_PROPERTY_PREFIX = "security.oauth2.client";
72+
73+
private static final String CLIENT_ID_PROPERTY = "client-id";
74+
75+
private static final String USER_INFO_URI_PROPERTY = "user-info-uri";
76+
77+
private static final String USER_NAME_ATTR_NAME_PROPERTY = "user-name-attribute-name";
78+
79+
static Set<String> getClientKeys(Environment environment) {
80+
return getClientPropertiesByClient(environment).keySet();
81+
}
82+
83+
static Map<String, Map> getClientPropertiesByClient(Environment environment) {
84+
Map<String, Object> clientPropertiesByClient = Binder.get(environment)
85+
.bind(CLIENT_PROPERTY_PREFIX, Bindable.mapOf(String.class, Object.class))
86+
.orElse(new HashMap<>());
87+
88+
// Filter out clients that don't have the client-id property set
89+
return clientPropertiesByClient.entrySet().stream()
90+
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), (Map) e.getValue()))
91+
.filter(e -> e.getValue().containsKey(CLIENT_ID_PROPERTY))
92+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
93+
}
94+
95+
@Order(1)
96+
@Configuration
97+
@Conditional(ClientsConfiguredCondition.class)
98+
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
99+
protected static class ClientRegistrationRepositoryConfiguration {
100+
private final Environment environment;
101+
102+
protected ClientRegistrationRepositoryConfiguration(Environment environment) {
103+
this.environment = environment;
104+
}
105+
106+
@Bean
107+
public ClientRegistrationRepository clientRegistrationRepository() {
108+
List<ClientRegistration> clientRegistrations = new ArrayList<>();
109+
110+
Binder binder = Binder.get(this.environment);
111+
getClientKeys(this.environment).forEach(clientKey -> {
112+
String fullClientKey = CLIENT_PROPERTY_PREFIX + "." + clientKey;
113+
ClientRegistrationProperties clientRegistrationProperties = binder.bind(
114+
fullClientKey, Bindable.of(ClientRegistrationProperties.class)).get();
115+
clientRegistrations.add(new ClientRegistration.Builder(clientRegistrationProperties).build());
116+
});
117+
118+
return new InMemoryClientRegistrationRepository(clientRegistrations);
119+
}
120+
}
121+
122+
@Order(2)
123+
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
124+
@ConditionalOnBean(ClientRegistrationRepository.class)
125+
@EnableWebSecurity
126+
protected static class OAuth2LoginConfiguration extends WebSecurityConfigurerAdapter {
127+
private final Environment environment;
128+
private final ClientRegistrationRepository clientRegistrationRepository;
129+
130+
protected OAuth2LoginConfiguration(Environment environment, ClientRegistrationRepository clientRegistrationRepository) {
131+
this.environment = environment;
132+
this.clientRegistrationRepository = clientRegistrationRepository;
133+
}
134+
135+
// @formatter:off
136+
@Override
137+
protected void configure(HttpSecurity http) throws Exception {
138+
http
139+
.authorizeRequests()
140+
.anyRequest().authenticated()
141+
.and()
142+
.oauth2Login()
143+
.clients(this.clientRegistrationRepository);
144+
145+
this.registerUserNameAttributeNames(http.oauth2Login());
146+
}
147+
// @formatter:on
148+
149+
private void registerUserNameAttributeNames(OAuth2LoginConfigurer<HttpSecurity> oauth2LoginConfigurer) throws Exception {
150+
getClientPropertiesByClient(this.environment).entrySet().stream().forEach(e -> {
151+
String userInfoUriValue = (String) e.getValue().get(USER_INFO_URI_PROPERTY);
152+
String userNameAttributeNameValue = (String) e.getValue().get(USER_NAME_ATTR_NAME_PROPERTY);
153+
if (userInfoUriValue != null && userNameAttributeNameValue != null) {
154+
// @formatter:off
155+
oauth2LoginConfigurer
156+
.userInfoEndpoint()
157+
.userNameAttributeName(userNameAttributeNameValue, URI.create(userInfoUriValue));
158+
// @formatter:on
159+
}
160+
});
161+
}
162+
}
163+
164+
private static class ClientsConfiguredCondition extends SpringBootCondition implements ConfigurationCondition {
165+
166+
@Override
167+
public ConfigurationCondition.ConfigurationPhase getConfigurationPhase() {
168+
return ConfigurationPhase.PARSE_CONFIGURATION;
169+
}
170+
171+
@Override
172+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
173+
ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Clients Configured Condition");
174+
Set<String> clientKeys = getClientKeys(context.getEnvironment());
175+
if (!clientKeys.isEmpty()) {
176+
return ConditionOutcome.match(message.foundExactly("OAuth2 Client(s) -> " +
177+
clientKeys.stream().collect(Collectors.joining(", "))));
178+
}
179+
return ConditionOutcome.noMatch(message.notAvailable("OAuth2 Client(s)"));
180+
}
181+
}
182+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Google
2+
security.oauth2.client.google.client-authentication-method=basic
3+
security.oauth2.client.google.authorization-grant-type=authorization_code
4+
security.oauth2.client.google.redirect-uri={scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}
5+
security.oauth2.client.google.scope=openid, profile, email, address, phone
6+
security.oauth2.client.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
7+
security.oauth2.client.google.token-uri=https://accounts.google.com/o/oauth2/token
8+
security.oauth2.client.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
9+
security.oauth2.client.google.jwk-set-uri=https://www.googleapis.com/oauth2/v3/certs
10+
security.oauth2.client.google.client-name=Google
11+
security.oauth2.client.google.client-alias=google
12+
13+
# GitHub
14+
security.oauth2.client.github.client-authentication-method=basic
15+
security.oauth2.client.github.authorization-grant-type=authorization_code
16+
security.oauth2.client.github.redirect-uri={scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}
17+
security.oauth2.client.github.scope=user
18+
security.oauth2.client.github.authorization-uri=https://github.com/login/oauth/authorize
19+
security.oauth2.client.github.token-uri=https://github.com/login/oauth/access_token
20+
security.oauth2.client.github.user-info-uri=https://api.github.com/user
21+
security.oauth2.client.github.user-name-attribute-name=name
22+
security.oauth2.client.github.client-name=GitHub
23+
security.oauth2.client.github.client-alias=github
24+
25+
# Facebook
26+
security.oauth2.client.facebook.client-authentication-method=post
27+
security.oauth2.client.facebook.authorization-grant-type=authorization_code
28+
security.oauth2.client.facebook.redirect-uri={scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}
29+
security.oauth2.client.facebook.scope=public_profile, email
30+
security.oauth2.client.facebook.authorization-uri=https://www.facebook.com/v2.8/dialog/oauth
31+
security.oauth2.client.facebook.token-uri=https://graph.facebook.com/v2.8/oauth/access_token
32+
security.oauth2.client.facebook.user-info-uri=https://graph.facebook.com/me
33+
security.oauth2.client.facebook.user-name-attribute-name=name
34+
security.oauth2.client.facebook.client-name=Facebook
35+
security.oauth2.client.facebook.client-alias=facebook
36+
37+
# Okta
38+
security.oauth2.client.okta.client-authentication-method=basic
39+
security.oauth2.client.okta.authorization-grant-type=authorization_code
40+
security.oauth2.client.okta.redirect-uri={scheme}://{serverName}:{serverPort}{contextPath}/oauth2/authorize/code/{clientAlias}
41+
security.oauth2.client.okta.scope=openid, profile, email, address, phone
42+
security.oauth2.client.okta.client-name=Okta
43+
security.oauth2.client.okta.client-alias=okta

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingIni
77
org.springframework.context.ApplicationListener=\
88
org.springframework.boot.autoconfigure.BackgroundPreinitializer
99

10+
# Environment Post Processors
11+
org.springframework.boot.env.EnvironmentPostProcessor=\
12+
org.springframework.boot.autoconfigure.security.oauth2.client.ClientPropertyDefaultsEnvironmentPostProcessor
13+
1014
# Auto Configuration Import Listeners
1115
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
1216
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
@@ -99,6 +103,7 @@ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration
99103
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
100104
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
101105
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
106+
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\
102107
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
103108
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
104109
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\

0 commit comments

Comments
 (0)