Commit f3e63093 authored by Phillip Webb's avatar Phillip Webb

Add OAuth PrincipalExtractor strategy interface

Update `UserInfoTokenServices` to use a PrincipalExtractor interface
to extract the principal.

Fixes gh-5186
parent 7f45485e
/*
* Copyright 2012-2016 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.resource;
import java.util.Map;
/**
* Default implementation of {@link PrincipalExtractor}. Extracts the principal from the
* map with well known keys.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class FixedPrincipalExtractor implements PrincipalExtractor {
private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
"userid", "user_id", "login", "id", "name" };
@Override
public Object extractPrincipal(Map<String, Object> map) {
for (String key : PRINCIPAL_KEYS) {
if (map.containsKey(key)) {
return map.get(key);
}
}
return null;
}
}
/*
* Copyright 2012-2016 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.resource;
import java.util.Map;
/**
* Strategy used by {@link UserInfoTokenServices} to extract the principal from the
* resource server's response.
*
* @author Phillip Webb
* @since 1.4.0
*/
public interface PrincipalExtractor {
/**
* Extract the principal that should be used for the token.
* @param map the source map
* @return the extracted principal or {@code null}
*/
Object extractPrincipal(Map<String, Object> map);
}
...@@ -129,14 +129,18 @@ public class ResourceServerTokenServicesConfiguration { ...@@ -129,14 +129,18 @@ public class ResourceServerTokenServicesConfiguration {
private final AuthoritiesExtractor authoritiesExtractor; private final AuthoritiesExtractor authoritiesExtractor;
private final PrincipalExtractor principalExtractor;
public SocialTokenServicesConfiguration(ResourceServerProperties sso, public SocialTokenServicesConfiguration(ResourceServerProperties sso,
ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactoryProvider, ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactoryProvider,
UserInfoRestTemplateFactory restTemplateFactory, UserInfoRestTemplateFactory restTemplateFactory,
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) { ObjectProvider<AuthoritiesExtractor> authoritiesExtractor,
ObjectProvider<PrincipalExtractor> principalExtractor) {
this.sso = sso; this.sso = sso;
this.connectionFactory = connectionFactoryProvider.getIfAvailable(); this.connectionFactory = connectionFactoryProvider.getIfAvailable();
this.restTemplate = restTemplateFactory.getUserInfoRestTemplate(); this.restTemplate = restTemplateFactory.getUserInfoRestTemplate();
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable(); this.authoritiesExtractor = authoritiesExtractor.getIfAvailable();
this.principalExtractor = principalExtractor.getIfAvailable();
} }
@Bean @Bean
...@@ -158,6 +162,9 @@ public class ResourceServerTokenServicesConfiguration { ...@@ -158,6 +162,9 @@ public class ResourceServerTokenServicesConfiguration {
if (this.authoritiesExtractor != null) { if (this.authoritiesExtractor != null) {
services.setAuthoritiesExtractor(this.authoritiesExtractor); services.setAuthoritiesExtractor(this.authoritiesExtractor);
} }
if (this.principalExtractor != null) {
services.setPrincipalExtractor(this.principalExtractor);
}
return services; return services;
} }
...@@ -174,12 +181,16 @@ public class ResourceServerTokenServicesConfiguration { ...@@ -174,12 +181,16 @@ public class ResourceServerTokenServicesConfiguration {
private final AuthoritiesExtractor authoritiesExtractor; private final AuthoritiesExtractor authoritiesExtractor;
private final PrincipalExtractor principalExtractor;
public UserInfoTokenServicesConfiguration(ResourceServerProperties sso, public UserInfoTokenServicesConfiguration(ResourceServerProperties sso,
UserInfoRestTemplateFactory restTemplateFactory, UserInfoRestTemplateFactory restTemplateFactory,
ObjectProvider<AuthoritiesExtractor> authoritiesExtractorProvider) { ObjectProvider<AuthoritiesExtractor> authoritiesExtractor,
ObjectProvider<PrincipalExtractor> principalExtractor) {
this.sso = sso; this.sso = sso;
this.restTemplate = restTemplateFactory.getUserInfoRestTemplate(); this.restTemplate = restTemplateFactory.getUserInfoRestTemplate();
this.authoritiesExtractor = authoritiesExtractorProvider.getIfAvailable(); this.authoritiesExtractor = authoritiesExtractor.getIfAvailable();
this.principalExtractor = principalExtractor.getIfAvailable();
} }
@Bean @Bean
...@@ -192,6 +203,9 @@ public class ResourceServerTokenServicesConfiguration { ...@@ -192,6 +203,9 @@ public class ResourceServerTokenServicesConfiguration {
if (this.authoritiesExtractor != null) { if (this.authoritiesExtractor != null) {
services.setAuthoritiesExtractor(this.authoritiesExtractor); services.setAuthoritiesExtractor(this.authoritiesExtractor);
} }
if (this.principalExtractor != null) {
services.setPrincipalExtractor(this.principalExtractor);
}
return services; return services;
} }
......
...@@ -35,6 +35,7 @@ import org.springframework.security.oauth2.common.exceptions.InvalidTokenExcepti ...@@ -35,6 +35,7 @@ import org.springframework.security.oauth2.common.exceptions.InvalidTokenExcepti
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.Assert;
/** /**
* {@link ResourceServerTokenServices} that uses a user info REST service. * {@link ResourceServerTokenServices} that uses a user info REST service.
...@@ -46,9 +47,6 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { ...@@ -46,9 +47,6 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
"userid", "user_id", "login", "id", "name" };
private final String userInfoEndpointUrl; private final String userInfoEndpointUrl;
private final String clientId; private final String clientId;
...@@ -59,6 +57,8 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { ...@@ -59,6 +57,8 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor(); private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();
public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) { public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
this.userInfoEndpointUrl = userInfoEndpointUrl; this.userInfoEndpointUrl = userInfoEndpointUrl;
this.clientId = clientId; this.clientId = clientId;
...@@ -73,9 +73,15 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { ...@@ -73,9 +73,15 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
} }
public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) { public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
Assert.notNull(authoritiesExtractor, "AuthoritiesExtractor must not be null");
this.authoritiesExtractor = authoritiesExtractor; this.authoritiesExtractor = authoritiesExtractor;
} }
public void setPrincipalExtractor(PrincipalExtractor principalExtractor) {
Assert.notNull(principalExtractor, "PrincipalExtractor must not be null");
this.principalExtractor = principalExtractor;
}
@Override @Override
public OAuth2Authentication loadAuthentication(String accessToken) public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException { throws AuthenticationException, InvalidTokenException {
...@@ -101,17 +107,13 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { ...@@ -101,17 +107,13 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
/** /**
* Return the principal that should be used for the token. The default implementation * Return the principal that should be used for the token. The default implementation
* looks for well know {@code user*} keys in the map. * delegates to the {@link PrincipalExtractor}.
* @param map the source map * @param map the source map
* @return the principal or {@literal "unknown"} * @return the principal or {@literal "unknown"}
*/ */
protected Object getPrincipal(Map<String, Object> map) { protected Object getPrincipal(Map<String, Object> map) {
for (String key : PRINCIPAL_KEYS) { Object principal = this.principalExtractor.extractPrincipal(map);
if (map.containsKey(key)) { return (principal == null ? "unknown" : principal);
return map.get(key);
}
}
return "unknown";
} }
@Override @Override
......
...@@ -125,6 +125,19 @@ public class ResourceServerTokenServicesConfigurationTests { ...@@ -125,6 +125,19 @@ public class ResourceServerTokenServicesConfigurationTests {
.containsExactly(this.context.getBean(AuthoritiesExtractor.class)); .containsExactly(this.context.getBean(AuthoritiesExtractor.class));
} }
@Test
public void userInfoWithPrincipal() {
EnvironmentTestUtils.addEnvironment(this.environment,
"security.oauth2.resource.userInfoUri:http://example.com");
this.context = new SpringApplicationBuilder(PrincipalConfiguration.class)
.environment(this.environment).web(false).run();
UserInfoTokenServices services = this.context
.getBean(UserInfoTokenServices.class);
assertThat(services).isNotNull();
assertThat(services).extracting("principalExtractor")
.containsExactly(this.context.getBean(PrincipalExtractor.class));
}
@Test @Test
public void userInfoWithClient() { public void userInfoWithClient() {
EnvironmentTestUtils.addEnvironment(this.environment, EnvironmentTestUtils.addEnvironment(this.environment,
...@@ -228,6 +241,23 @@ public class ResourceServerTokenServicesConfigurationTests { ...@@ -228,6 +241,23 @@ public class ResourceServerTokenServicesConfigurationTests {
} }
@Configuration
protected static class PrincipalConfiguration extends ResourceConfiguration {
@Bean
PrincipalExtractor authoritiesExtractor() {
return new PrincipalExtractor() {
@Override
public Object extractPrincipal(Map<String, Object> map) {
return "boot";
}
};
}
}
@Import({ OAuth2RestOperationsConfiguration.class }) @Import({ OAuth2RestOperationsConfiguration.class })
protected static class ResourceNoClientConfiguration extends ResourceConfiguration { protected static class ResourceNoClientConfiguration extends ResourceConfiguration {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment