Add OAuth PrincipalExtractor strategy interface
Update `UserInfoTokenServices` to use a PrincipalExtractor interface to extract the principal. Fixes gh-5186
This commit is contained in:
parent
7f45485e61
commit
f3e630933a
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
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
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
.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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue