Skip to content

Commit

Permalink
Add an AuthoritiesExtractor strategy for UserInfoTokenServices
Browse files Browse the repository at this point in the history
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 spring-projectsgh-3711
  • Loading branch information
dsyer committed Oct 8, 2015
1 parent eadd20e commit 4768faa
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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<GrantedAuthority> extractAuthorities(Map<String, Object> map);

}
Original file line number Diff line number Diff line change
@@ -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<GrantedAuthority> extractAuthorities(Map<String, Object> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ protected static class SocialTokenServicesConfiguration {
@Qualifier("userInfoRestTemplate")
private OAuth2RestOperations restTemplate;

@Autowired(required = false)
private AuthoritiesExtractor authoritiesExtractor;

@Bean
@ConditionalOnBean(ConnectionFactoryLocator.class)
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
Expand All @@ -187,6 +190,9 @@ public UserInfoTokenServices userInfoTokenServices() {
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;
}

Expand All @@ -204,13 +210,19 @@ protected static class UserInfoTokenServicesConfiguration {
@Qualifier("userInfoRestTemplate")
private OAuth2RestOperations restTemplate;

@Autowired(required = false)
private AuthoritiesExtractor authoritiesExtractor;

@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public UserInfoTokenServices userInfoTokenServices() {
UserInfoTokenServices services = new UserInfoTokenServices(
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand Down Expand Up @@ -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;
Expand All @@ -70,6 +71,10 @@ public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}

public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
this.authoritiesExtractor = authoritiesExtractor;
}

@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Expand All @@ -83,8 +88,8 @@ public OAuth2Authentication loadAuthentication(String accessToken)

private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
List<GrantedAuthority> authorities = this.authoritiesExtractor
.extractAuthorities(map);
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> map = new LinkedHashMap<String, Object>();

@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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,9 +40,12 @@
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;
Expand Down Expand Up @@ -101,6 +107,19 @@ public void switchToUserInfo() {
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,
Expand Down Expand Up @@ -172,6 +191,23 @@ protected static class ResourceConfiguration {

}

@Configuration
protected static class AuthoritiesConfiguration extends ResourceConfiguration {

@Bean
AuthoritiesExtractor authoritiesExtractor() {
return new AuthoritiesExtractor() {

@Override
public List<GrantedAuthority> extractAuthorities(
Map<String, Object> map) {
return AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
};
}
}

@Import({ OAuth2RestOperationsConfiguration.class })
protected static class ResourceNoClientConfiguration extends ResourceConfiguration {

Expand Down

0 comments on commit 4768faa

Please sign in to comment.