From 4768faaba771e35301b0ac68abf09cdb0e2f6881 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 8 Oct 2015 12:08:36 +0100 Subject: [PATCH] Add an AuthoritiesExtractor strategy for UserInfoTokenServices Default will extract an "authorities" key from the map coming from the server. No existing servers I am aware of actually send that data, but it might be helpful as a default nevertheless. User can override the default by adding a bean of that type. Fixes gh-3711 --- .../oauth2/resource/AuthoritiesExtractor.java | 31 +++++++++ .../resource/FixedAuthoritiesExtractor.java | 54 ++++++++++++++++ ...ourceServerTokenServicesConfiguration.java | 12 ++++ .../resource/UserInfoTokenServices.java | 11 +++- .../FixedAuthoritiesExtractorTests.java | 64 +++++++++++++++++++ ...ServerTokenServicesConfigurationTests.java | 36 +++++++++++ 6 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/AuthoritiesExtractor.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractor.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractorTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/AuthoritiesExtractor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/AuthoritiesExtractor.java new file mode 100644 index 0000000000..e06a07bdbb --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/AuthoritiesExtractor.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2015 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.List; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; + +/** + * @author Dave Syer + */ +public interface AuthoritiesExtractor { + + List extractAuthorities(Map map); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractor.java new file mode 100644 index 0000000000..8a8bd85837 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2015 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.Collection; +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * @author Dave Syer + */ +public class FixedAuthoritiesExtractor implements AuthoritiesExtractor { + + private static final String AUTHORITIES = "authorities"; + + @Override + public List extractAuthorities(Map map) { + String authorities = "ROLE_USER"; + if (map.containsKey(AUTHORITIES)) { + Object object = map.get(AUTHORITIES); + if (object instanceof Collection) { + authorities = StringUtils + .collectionToCommaDelimitedString((Collection) object); + } + else if (ObjectUtils.isArray(object)) { + authorities = StringUtils.arrayToCommaDelimitedString((Object[]) object); + } + else if (object != null) { + authorities = object.toString(); + } + } + return AuthorityUtils.commaSeparatedStringToAuthorityList(authorities); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java index 17592c462c..6927dc64a4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java @@ -171,6 +171,9 @@ public class ResourceServerTokenServicesConfiguration { @Qualifier("userInfoRestTemplate") private OAuth2RestOperations restTemplate; + @Autowired(required = false) + private AuthoritiesExtractor authoritiesExtractor; + @Bean @ConditionalOnBean(ConnectionFactoryLocator.class) @ConditionalOnMissingBean(ResourceServerTokenServices.class) @@ -187,6 +190,9 @@ public class ResourceServerTokenServicesConfiguration { this.sso.getUserInfoUri(), this.sso.getClientId()); services.setTokenType(this.sso.getTokenType()); services.setRestTemplate(this.restTemplate); + if (this.authoritiesExtractor != null) { + services.setAuthoritiesExtractor(this.authoritiesExtractor); + } return services; } @@ -204,6 +210,9 @@ public class ResourceServerTokenServicesConfiguration { @Qualifier("userInfoRestTemplate") private OAuth2RestOperations restTemplate; + @Autowired(required = false) + private AuthoritiesExtractor authoritiesExtractor; + @Bean @ConditionalOnMissingBean(ResourceServerTokenServices.class) public UserInfoTokenServices userInfoTokenServices() { @@ -211,6 +220,9 @@ public class ResourceServerTokenServicesConfiguration { this.sso.getUserInfoUri(), this.sso.getClientId()); services.setRestTemplate(this.restTemplate); services.setTokenType(this.sso.getTokenType()); + if (this.authoritiesExtractor != null) { + services.setAuthoritiesExtractor(this.authoritiesExtractor); + } return services; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoTokenServices.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoTokenServices.java index cfbed8c623..0feb6be05e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoTokenServices.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/UserInfoTokenServices.java @@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; @@ -57,6 +56,8 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE; + private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor(); + public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) { this.userInfoEndpointUrl = userInfoEndpointUrl; this.clientId = clientId; @@ -70,6 +71,10 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { this.restTemplate = restTemplate; } + public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) { + this.authoritiesExtractor = authoritiesExtractor; + } + @Override public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException { @@ -83,8 +88,8 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { private OAuth2Authentication extractAuthentication(Map map) { Object principal = getPrincipal(map); - List authorities = AuthorityUtils - .commaSeparatedStringToAuthorityList("ROLE_USER"); + List authorities = this.authoritiesExtractor + .extractAuthorities(map); OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null, null, null, null, null); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractorTests.java new file mode 100644 index 0000000000..d3b5a09a1a --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/FixedAuthoritiesExtractorTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2015 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.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Dave Syer + */ +public class FixedAuthoritiesExtractorTests { + + private FixedAuthoritiesExtractor extractor = new FixedAuthoritiesExtractor(); + + private Map map = new LinkedHashMap(); + + @Test + public void authorities() { + this.map.put("authorities", "ROLE_ADMIN"); + assertEquals("[ROLE_ADMIN]", + this.extractor.extractAuthorities(this.map).toString()); + } + + @Test + public void authoritiesCommaSeparated() { + this.map.put("authorities", "ROLE_USER,ROLE_ADMIN"); + assertEquals("[ROLE_USER, ROLE_ADMIN]", + this.extractor.extractAuthorities(this.map).toString()); + } + + @Test + public void authoritiesArray() { + this.map.put("authorities", new String[] { "ROLE_USER", "ROLE_ADMIN" }); + assertEquals("[ROLE_USER, ROLE_ADMIN]", + this.extractor.extractAuthorities(this.map).toString()); + } + + @Test + public void authoritiesList() { + this.map.put("authorities", Arrays.asList("ROLE_USER", "ROLE_ADMIN")); + assertEquals("[ROLE_USER, ROLE_ADMIN]", + this.extractor.extractAuthorities(this.map).toString()); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java index 263241f03f..df97746b55 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfigurationTests.java @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource; +import java.util.List; +import java.util.Map; + import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -37,9 +40,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.social.connect.ConnectionFactoryLocator; +import org.springframework.test.util.ReflectionTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -101,6 +107,19 @@ public class ResourceServerTokenServicesConfigurationTests { assertNotNull(services); } + @Test + public void userInfoWithAuthorities() { + EnvironmentTestUtils.addEnvironment(this.environment, + "security.oauth2.resource.userInfoUri:http://example.com"); + this.context = new SpringApplicationBuilder(AuthoritiesConfiguration.class) + .environment(this.environment).web(false).run(); + UserInfoTokenServices services = this.context + .getBean(UserInfoTokenServices.class); + assertNotNull(services); + assertEquals(this.context.getBean(AuthoritiesExtractor.class), + ReflectionTestUtils.getField(services, "authoritiesExtractor")); + } + @Test public void userInfoNoClient() { EnvironmentTestUtils.addEnvironment(this.environment, @@ -172,6 +191,23 @@ public class ResourceServerTokenServicesConfigurationTests { } + @Configuration + protected static class AuthoritiesConfiguration extends ResourceConfiguration { + + @Bean + AuthoritiesExtractor authoritiesExtractor() { + return new AuthoritiesExtractor() { + + @Override + public List extractAuthorities( + Map map) { + return AuthorityUtils + .commaSeparatedStringToAuthorityList("ROLE_ADMIN"); + } + }; + } + } + @Import({ OAuth2RestOperationsConfiguration.class }) protected static class ResourceNoClientConfiguration extends ResourceConfiguration {