ソースを参照

Split up NimbusOAuth2UserService

Fixes gh-4447
Joe Grandja 8 年 前
コミット
0e9b2807bf
16 ファイル変更519 行追加286 行削除
  1. 27 10
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java
  2. 2 1
      oauth2/oauth2-client/spring-security-oauth2-client.gradle
  3. 97 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/CustomUserTypesOAuth2UserService.java
  4. 96 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DefaultOAuth2UserService.java
  5. 56 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DelegatingOAuth2UserService.java
  6. 3 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java
  7. 37 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/UserInfoRetriever.java
  8. 2 2
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusClientHttpResponse.java
  9. 103 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusUserInfoRetriever.java
  10. 0 232
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusOAuth2UserService.java
  11. 78 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/user/OidcUserService.java
  12. 1 6
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java
  13. 4 16
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java
  14. 11 0
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java
  15. 1 12
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java
  16. 1 0
      samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle

+ 27 - 10
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java

@@ -20,23 +20,26 @@ import org.springframework.security.config.annotation.web.configurers.AbstractAu
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 import org.springframework.security.jwt.JwtDecoder;
 import org.springframework.security.jwt.nimbus.NimbusJwtDecoderJwkSupport;
-import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationProcessingFilter;
 import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
 import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
-import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
 import org.springframework.security.oauth2.client.authentication.jwt.DefaultProviderJwtDecoderRegistry;
 import org.springframework.security.oauth2.client.authentication.jwt.ProviderJwtDecoderRegistry;
-import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository;
 import org.springframework.security.oauth2.client.token.SecurityTokenRepository;
+import org.springframework.security.oauth2.client.user.CustomUserTypesOAuth2UserService;
+import org.springframework.security.oauth2.client.user.DefaultOAuth2UserService;
+import org.springframework.security.oauth2.client.user.DelegatingOAuth2UserService;
 import org.springframework.security.oauth2.client.user.OAuth2UserService;
-import org.springframework.security.oauth2.client.user.web.nimbus.NimbusOAuth2UserService;
+import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationProcessingFilter;
+import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger;
+import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger;
 import org.springframework.security.oauth2.core.AccessToken;
 import org.springframework.security.oauth2.core.provider.DefaultProviderMetadata;
 import org.springframework.security.oauth2.core.provider.ProviderMetadata;
 import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.oidc.client.user.OidcUserService;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
 import org.springframework.util.Assert;
@@ -46,7 +49,9 @@ import org.springframework.web.util.UriComponentsBuilder;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -197,16 +202,28 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
 		return new DefaultProviderJwtDecoderRegistry(jwtDecoders);
 	}
 
+	private boolean isOidcClientRegistered() {
+		ClientRegistrationRepository clientRegistrationRepository = OAuth2LoginConfigurer.getClientRegistrationRepository(this.getBuilder());
+		return clientRegistrationRepository.getRegistrations()
+			.stream()
+			.anyMatch(registration ->
+				registration.getScope().stream().anyMatch(scope -> scope.equalsIgnoreCase("openid")));
+
+	}
+
 	private OAuth2UserService getUserInfoService() {
 		if (this.userInfoService == null) {
-			NimbusOAuth2UserService nimbusOAuth2UserService = new NimbusOAuth2UserService();
-			if (!this.customUserTypes.isEmpty()) {
-				nimbusOAuth2UserService.setCustomUserTypes(this.customUserTypes);
-			}
+			List<OAuth2UserService> oauth2UserServices = new ArrayList<>();
 			if (!this.userNameAttributeNames.isEmpty()) {
-				nimbusOAuth2UserService.setUserNameAttributeNames(this.userNameAttributeNames);
+				oauth2UserServices.add(new DefaultOAuth2UserService(this.userNameAttributeNames));
+			}
+			if (this.isOidcClientRegistered()) {
+				oauth2UserServices.add(new OidcUserService());
+			}
+			if (!this.customUserTypes.isEmpty()) {
+				oauth2UserServices.add(new CustomUserTypesOAuth2UserService(this.customUserTypes));
 			}
-			this.userInfoService = nimbusOAuth2UserService;
+			this.userInfoService = new DelegatingOAuth2UserService(oauth2UserServices);
 		}
 		return this.userInfoService;
 	}

+ 2 - 1
oauth2/oauth2-client/spring-security-oauth2-client.gradle

@@ -3,11 +3,12 @@ apply plugin: 'io.spring.convention.spring-module'
 dependencies {
 	compile project(':spring-security-core')
 	compile project(':spring-security-oauth2-core')
-	compile project(':spring-security-jwt-jose')
 	compile project(':spring-security-web')
 	compile springCoreDependency
 	compile 'org.springframework:spring-web'
 	compile 'com.nimbusds:oauth2-oidc-sdk'
 
+	optional project(':spring-security-jwt-jose')
+
 	provided 'javax.servlet:javax.servlet-api'
 }

+ 97 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/CustomUserTypesOAuth2UserService.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.client.user;
+
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.PropertyAccessorFactory;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
+import org.springframework.util.Assert;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An implementation of an {@link OAuth2UserService} that supports custom {@link OAuth2User} types.
+ * <p>
+ * The custom user type(s) is supplied via the constructor,
+ * using a <code>Map</code> of {@link OAuth2User} type <i>keyed</i> by <code>URI</code>,
+ * representing the <i>UserInfo Endpoint</i> address.
+ * <p>
+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
+ * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2UserService
+ * @see OAuth2User
+ * @see UserInfoRetriever
+ */
+public class CustomUserTypesOAuth2UserService implements OAuth2UserService {
+	private final Map<URI, Class<? extends OAuth2User>> customUserTypes;
+	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
+
+	public CustomUserTypesOAuth2UserService(Map<URI, Class<? extends OAuth2User>> customUserTypes) {
+		Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty");
+		this.customUserTypes = Collections.unmodifiableMap(new LinkedHashMap<>(customUserTypes));
+	}
+
+	@Override
+	public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException {
+		URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri());
+		Class<? extends OAuth2User> customUserType;
+		if ((customUserType = this.getCustomUserTypes().get(userInfoUri)) == null) {
+			return null;
+		}
+
+		OAuth2User customUser;
+		try {
+			customUser = customUserType.newInstance();
+		} catch (ReflectiveOperationException ex) {
+			throw new IllegalArgumentException("An error occurred while attempting to instantiate the custom OAuth2User \"" +
+				customUserType.getName() + "\": " + ex.getMessage(), ex);
+		}
+
+		Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(clientAuthentication);
+		if (OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) {
+			userAttributes.putAll(((OidcClientAuthenticationToken)clientAuthentication).getIdToken().getClaims());
+		}
+
+		BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(customUser);
+		wrapper.setAutoGrowNestedPaths(true);
+		wrapper.setPropertyValues(userAttributes);
+
+		return customUser;
+	}
+
+	protected Map<URI, Class<? extends OAuth2User>> getCustomUserTypes() {
+		return this.customUserTypes;
+	}
+
+	protected UserInfoRetriever getUserInfoRetriever() {
+		return this.userInfoRetriever;
+	}
+
+	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
+		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
+		this.userInfoRetriever = userInfoRetriever;
+	}
+}

+ 96 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DefaultOAuth2UserService.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.client.user;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever;
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
+import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
+import org.springframework.util.Assert;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of an {@link OAuth2UserService} that supports standard <i>OAuth 2.0 Provider's</i>.
+ * <p>
+ * For standard <i>OAuth 2.0 Provider's</i>, the attribute name (from the <i>UserInfo Response</i>)
+ * for the <i>&quot;user's name&quot;</i> is required. This is supplied via the constructor,
+ * mapped by <code>URI</code>, which represents the <i>UserInfo Endpoint</i> address.
+ * <p>
+ * <b>NOTE:</b> Attribute names are <b><i>not</i></b> standardized between providers and therefore will vary.
+ * Please consult the provider's API documentation for the set of supported user attribute names.
+ * <p>
+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
+ * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2UserService
+ * @see DefaultOAuth2User
+ * @see UserInfoRetriever
+ */
+public class DefaultOAuth2UserService implements OAuth2UserService {
+	private final Map<URI, String> userNameAttributeNames;
+	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
+
+	public DefaultOAuth2UserService(Map<URI, String> userNameAttributeNames) {
+		Assert.notEmpty(userNameAttributeNames, "userNameAttributeNames cannot be empty");
+		this.userNameAttributeNames = Collections.unmodifiableMap(new LinkedHashMap<>(userNameAttributeNames));
+	}
+
+	@Override
+	public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException {
+		if (OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) {
+			return null;
+		}
+
+		URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri());
+		if (!this.getUserNameAttributeNames().containsKey(userInfoUri)) {
+			throw new IllegalArgumentException(
+				"Missing required \"user name\" attribute name for UserInfo Endpoint: " + userInfoUri.toString());
+		}
+		String userNameAttributeName = this.getUserNameAttributeNames().get(userInfoUri);
+
+		Map<String, Object> userAttributes = this.getUserInfoRetriever().retrieve(clientAuthentication);
+		GrantedAuthority authority = new OAuth2UserAuthority(userAttributes);
+		Set<GrantedAuthority> authorities = new HashSet<>();
+		authorities.add(authority);
+
+		return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
+	}
+
+	protected Map<URI, String> getUserNameAttributeNames() {
+		return this.userNameAttributeNames;
+	}
+
+	protected UserInfoRetriever getUserInfoRetriever() {
+		return this.userInfoRetriever;
+	}
+
+	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
+		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
+		this.userInfoRetriever = userInfoRetriever;
+	}
+}

+ 56 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DelegatingOAuth2UserService.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.client.user;
+
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An implementation of an {@link OAuth2UserService} that simply delegates
+ * to it's internal <code>List</code> of {@link OAuth2UserService}'s.
+ * <p>
+ * Each {@link OAuth2UserService} is given a chance to
+ * {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken) load} an {@link OAuth2User}
+ * with the first <code>non-null</code> {@link OAuth2User} being returned.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2UserService
+ * @see OAuth2User
+ */
+public class DelegatingOAuth2UserService implements OAuth2UserService {
+	private final List<OAuth2UserService> oauth2UserServices;
+
+	public DelegatingOAuth2UserService(List<OAuth2UserService> oauth2UserServices) {
+		Assert.notEmpty(oauth2UserServices, "oauth2UserServices cannot be empty");
+		this.oauth2UserServices = oauth2UserServices;
+	}
+
+	@Override
+	public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException {
+		OAuth2User oauth2User = this.oauth2UserServices.stream()
+			.map(oauth2UserService -> oauth2UserService.loadUser(clientAuthentication))
+			.filter(Objects::nonNull)
+			.findFirst()
+			.orElse(null);
+		return oauth2User;
+	}
+}

+ 3 - 7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java

@@ -19,12 +19,10 @@ import org.springframework.security.core.AuthenticatedPrincipal;
 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
 import org.springframework.security.oauth2.core.user.OAuth2User;
-import org.springframework.security.oauth2.oidc.core.UserInfo;
-import org.springframework.security.oauth2.oidc.core.user.OidcUser;
 
 /**
- * Implementations of this interface are responsible for obtaining
- * the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>
+ * Implementations of this interface are responsible for obtaining the user attributes
+ * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
  * using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()}
  * and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}.
  *
@@ -33,11 +31,9 @@ import org.springframework.security.oauth2.oidc.core.user.OidcUser;
  * @see OAuth2ClientAuthenticationToken
  * @see AuthenticatedPrincipal
  * @see OAuth2User
- * @see OidcUser
- * @see UserInfo
  */
 public interface OAuth2UserService {
 
-	OAuth2User loadUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException;
+	OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException;
 
 }

+ 37 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/UserInfoRetriever.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.client.user;
+
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+
+import java.util.Map;
+
+/**
+ * A strategy for retrieving the user attributes
+ * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
+ * using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()}.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2ClientAuthenticationToken
+ * @see OAuth2UserService
+ */
+public interface UserInfoRetriever {
+
+	Map<String, Object> retrieve(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException;
+
+}

+ 2 - 2
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusClientHttpResponse.java → oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusClientHttpResponse.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.client.user.web.nimbus;
+package org.springframework.security.oauth2.client.user.nimbus;
 
 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
 import org.springframework.http.HttpHeaders;
@@ -27,7 +27,7 @@ import java.io.InputStream;
 import java.nio.charset.Charset;
 
 /**
- * An implementation of a {@link ClientHttpResponse} which is used by {@link NimbusOAuth2UserService}.
+ * An implementation of a {@link ClientHttpResponse} which is used by {@link NimbusUserInfoRetriever}.
  *
  * <p>
  * <b>NOTE:</b> This class is intended for internal use only.

+ 103 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusUserInfoRetriever.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.client.user.nimbus;
+
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.client.user.UserInfoRetriever;
+import org.springframework.security.oauth2.core.OAuth2Error;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * An implementation of a {@link UserInfoRetriever} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see UserInfoRetriever
+ * @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
+ */
+public class NimbusUserInfoRetriever implements UserInfoRetriever {
+	private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
+	private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
+
+	@Override
+	public Map<String, Object> retrieve(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException {
+		URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri());
+		BearerAccessToken accessToken = new BearerAccessToken(clientAuthentication.getAccessToken().getTokenValue());
+
+		UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
+		HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
+		httpRequest.setConnectTimeout(30000);
+		httpRequest.setReadTimeout(30000);
+		HTTPResponse httpResponse;
+
+		try {
+			httpResponse = httpRequest.send();
+		} catch (IOException ex) {
+			throw new AuthenticationServiceException("An error occurred while sending the UserInfo Request: " +
+				ex.getMessage(), ex);
+		}
+
+		if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) {
+			UserInfoErrorResponse userInfoErrorResponse;
+			try {
+				userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse);
+			} catch (ParseException ex) {
+				OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
+					"An error occurred parsing the UserInfo Error response: " + ex.getMessage(), null);
+				throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
+			}
+			ErrorObject errorObject = userInfoErrorResponse.getErrorObject();
+
+			StringBuilder errorDescription = new StringBuilder();
+			errorDescription.append("An error occurred while attempting to access the UserInfo Endpoint -> ");
+			errorDescription.append("Error details: [");
+			errorDescription.append("UserInfo Uri: ").append(userInfoUri.toString());
+			errorDescription.append(", Http Status: ").append(errorObject.getHTTPStatusCode());
+			if (errorObject.getCode() != null) {
+				errorDescription.append(", Error Code: ").append(errorObject.getCode());
+			}
+			if (errorObject.getDescription() != null) {
+				errorDescription.append(", Error Description: ").append(errorObject.getDescription());
+			}
+			errorDescription.append("]");
+
+			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorDescription.toString(), null);
+			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
+		}
+
+		try {
+			return (Map<String, Object>) this.jackson2HttpMessageConverter.read(Map.class, new NimbusClientHttpResponse(httpResponse));
+		} catch (IOException ex) {
+			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
+				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
+			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
+		}
+	}
+}

+ 0 - 232
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusOAuth2UserService.java

@@ -1,232 +0,0 @@
-/*
- * Copyright 2012-2017 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.security.oauth2.client.user.web.nimbus;
-
-import com.nimbusds.oauth2.sdk.ErrorObject;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.http.HTTPRequest;
-import com.nimbusds.oauth2.sdk.http.HTTPResponse;
-import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
-import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
-import com.nimbusds.openid.connect.sdk.UserInfoRequest;
-import org.springframework.beans.BeanWrapper;
-import org.springframework.beans.PropertyAccessorFactory;
-import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.security.authentication.AuthenticationServiceException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
-import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
-import org.springframework.security.oauth2.client.registration.ClientRegistration;
-import org.springframework.security.oauth2.client.user.OAuth2UserService;
-import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
-import org.springframework.security.oauth2.core.user.OAuth2User;
-import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
-import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
-import org.springframework.security.oauth2.oidc.core.UserInfo;
-import org.springframework.security.oauth2.oidc.core.user.DefaultOidcUser;
-import org.springframework.security.oauth2.oidc.core.user.OidcUser;
-import org.springframework.security.oauth2.oidc.core.user.OidcUserAuthority;
-import org.springframework.util.Assert;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An implementation of an {@link OAuth2UserService} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
- *
- * <p>
- * This implementation may be configured with a <code>Map</code> of custom {@link OAuth2User} types
- * <i>keyed</i> by <code>URI</code>, which represents the <i>UserInfo Endpoint</i> address.
- *
- * <p>
- * For {@link OAuth2User}'s registered at a standard <i>OAuth 2.0 Provider</i>, the attribute name
- * for the &quot;user's name&quot; is required. This can be supplied via {@link #setUserNameAttributeNames(Map)},
- * <i>keyed</i> by <code>URI</code>, which represents the <i>UserInfo Endpoint</i> address.
- *
- * @author Joe Grandja
- * @since 5.0
- * @see OAuth2ClientAuthenticationToken
- * @see OidcClientAuthenticationToken
- * @see OAuth2User
- * @see OidcUser
- * @see UserInfo
- * @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
- */
-public class NimbusOAuth2UserService implements OAuth2UserService {
-	private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
-	private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
-	private Map<URI, String> userNameAttributeNames = Collections.unmodifiableMap(Collections.emptyMap());
-	private Map<URI, Class<? extends OAuth2User>> customUserTypes = Collections.unmodifiableMap(Collections.emptyMap());
-
-	public NimbusOAuth2UserService() {
-	}
-
-	@Override
-	public final OAuth2User loadUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException {
-		URI userInfoUri = this.getUserInfoUri(token);
-
-		if (this.getCustomUserTypes().containsKey(userInfoUri)) {
-			return this.loadCustomUser(token);
-		}
-		if (OidcClientAuthenticationToken.class.isAssignableFrom(token.getClass())) {
-			return this.loadOidcUser((OidcClientAuthenticationToken)token);
-		}
-
-		return this.loadOAuth2User(token);
-	}
-
-	protected OidcUser loadOidcUser(OidcClientAuthenticationToken token) throws OAuth2AuthenticationException {
-		// TODO Retrieving the UserInfo should be optional. Need to add the capability for opting in/out
-		Map<String, Object> userAttributes = this.getUserInfo(token);
-		UserInfo userInfo = new UserInfo(userAttributes);
-
-		GrantedAuthority authority = new OidcUserAuthority(token.getIdToken(), userInfo);
-		Set<GrantedAuthority> authorities = new HashSet<>();
-		authorities.add(authority);
-
-		return new DefaultOidcUser(authorities, token.getIdToken(), userInfo);
-	}
-
-	protected OAuth2User loadOAuth2User(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException {
-		URI userInfoUri = this.getUserInfoUri(token);
-		if (!this.getUserNameAttributeNames().containsKey(userInfoUri)) {
-			throw new IllegalArgumentException("The attribute name for the \"user's name\" is required for the OAuth2User " +
-				" retrieved from the UserInfo Endpoint -> " + userInfoUri.toString());
-		}
-		String userNameAttributeName = this.getUserNameAttributeNames().get(userInfoUri);
-
-		Map<String, Object> userAttributes = this.getUserInfo(token);
-
-		GrantedAuthority authority = new OAuth2UserAuthority(userAttributes);
-		Set<GrantedAuthority> authorities = new HashSet<>();
-		authorities.add(authority);
-
-		return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
-	}
-
-	protected OAuth2User loadCustomUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException {
-		URI userInfoUri = this.getUserInfoUri(token);
-		Class<? extends OAuth2User> customUserType = this.getCustomUserTypes().get(userInfoUri);
-
-		OAuth2User user;
-		try {
-			user = customUserType.newInstance();
-		} catch (ReflectiveOperationException ex) {
-			throw new IllegalArgumentException("An error occurred while attempting to instantiate the custom OAuth2User \"" +
-				customUserType.getName() + "\" -> " + ex.getMessage(), ex);
-		}
-
-		Map<String, Object> userAttributes = this.getUserInfo(token);
-		if (OidcClientAuthenticationToken.class.isAssignableFrom(token.getClass())) {
-			userAttributes.putAll(((OidcClientAuthenticationToken)token).getIdToken().getClaims());
-		}
-		BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(user);
-		wrapper.setAutoGrowNestedPaths(true);
-		wrapper.setPropertyValues(userAttributes);
-
-		return user;
-	}
-
-	protected Map<String, Object> getUserInfo(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException {
-		URI userInfoUri = this.getUserInfoUri(token);
-
-		BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().getTokenValue());
-
-		UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
-		HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
-		httpRequest.setConnectTimeout(30000);
-		httpRequest.setReadTimeout(30000);
-		HTTPResponse httpResponse;
-
-		try {
-			httpResponse = httpRequest.send();
-		} catch (IOException ex) {
-			throw new AuthenticationServiceException("An error occurred while sending the UserInfo Request: " +
-				ex.getMessage(), ex);
-		}
-
-		if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) {
-			UserInfoErrorResponse userInfoErrorResponse;
-			try {
-				userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse);
-			} catch (ParseException ex) {
-				OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
-					"An error occurred parsing the UserInfo Error response: " + ex.getMessage(), null);
-				throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
-			}
-			ErrorObject errorObject = userInfoErrorResponse.getErrorObject();
-
-			StringBuilder errorDescription = new StringBuilder();
-			errorDescription.append("An error occurred while attempting to access the UserInfo Endpoint -> ");
-			errorDescription.append("Error details: [");
-			errorDescription.append("UserInfo Uri: ").append(userInfoUri.toString());
-			errorDescription.append(", Http Status: ").append(errorObject.getHTTPStatusCode());
-			if (errorObject.getCode() != null) {
-				errorDescription.append(", Error Code: ").append(errorObject.getCode());
-			}
-			if (errorObject.getDescription() != null) {
-				errorDescription.append(", Error Description: ").append(errorObject.getDescription());
-			}
-			errorDescription.append("]");
-
-			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorDescription.toString(), null);
-			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
-		}
-
-		try {
-			return (Map<String, Object>) this.jackson2HttpMessageConverter.read(Map.class, new NimbusClientHttpResponse(httpResponse));
-		} catch (IOException ex) {
-			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
-				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
-			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
-		}
-	}
-
-	protected Map<URI, String> getUserNameAttributeNames() {
-		return this.userNameAttributeNames;
-	}
-
-	public final void setUserNameAttributeNames(Map<URI, String> userNameAttributeNames) {
-		Assert.notEmpty(userNameAttributeNames, "userNameAttributeNames cannot be empty");
-		this.userNameAttributeNames = Collections.unmodifiableMap(new HashMap<>(userNameAttributeNames));
-	}
-
-	protected Map<URI, Class<? extends OAuth2User>> getCustomUserTypes() {
-		return this.customUserTypes;
-	}
-
-	public final void setCustomUserTypes(Map<URI, Class<? extends OAuth2User>> customUserTypes) {
-		Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty");
-		this.customUserTypes = Collections.unmodifiableMap(new HashMap<>(customUserTypes));
-	}
-
-	private URI getUserInfoUri(OAuth2ClientAuthenticationToken token) {
-		ClientRegistration clientRegistration = token.getClientRegistration();
-		try {
-			return new URI(clientRegistration.getProviderDetails().getUserInfoUri());
-		} catch (Exception ex) {
-			throw new IllegalArgumentException("An error occurred parsing the UserInfo URI: " +
-				clientRegistration.getProviderDetails().getUserInfoUri(), ex);
-		}
-	}
-}

+ 78 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/user/OidcUserService.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2017 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.security.oauth2.oidc.client.user;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.client.user.OAuth2UserService;
+import org.springframework.security.oauth2.client.user.UserInfoRetriever;
+import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken;
+import org.springframework.security.oauth2.oidc.core.UserInfo;
+import org.springframework.security.oauth2.oidc.core.user.DefaultOidcUser;
+import org.springframework.security.oauth2.oidc.core.user.OidcUserAuthority;
+import org.springframework.util.Assert;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of an {@link OAuth2UserService} that supports <i>OpenID Connect 1.0 Provider's</i>.
+ * <p>
+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
+ * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
+ * and constructs a {@link UserInfo} instance.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2UserService
+ * @see OidcClientAuthenticationToken
+ * @see DefaultOidcUser
+ * @see UserInfo
+ * @see UserInfoRetriever
+ */
+public class OidcUserService implements OAuth2UserService {
+	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
+
+	@Override
+	public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException {
+		if (!OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) {
+			return null;
+		}
+		OidcClientAuthenticationToken oidcClientAuthentication = (OidcClientAuthenticationToken)clientAuthentication;
+
+		Map<String, Object> userAttributes = this.getUserInfoRetriever().retrieve(oidcClientAuthentication);
+		UserInfo userInfo = new UserInfo(userAttributes);
+
+		GrantedAuthority authority = new OidcUserAuthority(oidcClientAuthentication.getIdToken(), userInfo);
+		Set<GrantedAuthority> authorities = new HashSet<>();
+		authorities.add(authority);
+
+		return new DefaultOidcUser(authorities, oidcClientAuthentication.getIdToken(), userInfo);
+	}
+
+	protected UserInfoRetriever getUserInfoRetriever() {
+		return this.userInfoRetriever;
+	}
+
+	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
+		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
+		this.userInfoRetriever = userInfoRetriever;
+	}
+}

+ 1 - 6
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java

@@ -43,7 +43,7 @@ public class OAuth2UserAuthority implements GrantedAuthority {
 		Assert.hasText(authority, "authority cannot be empty");
 		Assert.notEmpty(attributes, "attributes cannot be empty");
 		this.authority = authority;
-		this.setAttributes(attributes);
+		this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
 	}
 
 	@Override
@@ -55,11 +55,6 @@ public class OAuth2UserAuthority implements GrantedAuthority {
 		return this.attributes;
 	}
 
-	protected final void setAttributes(Map<String, Object> attributes) {
-		Assert.notEmpty(attributes, "attributes cannot be empty");
-		this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
-	}
-
 	@Override
 	public boolean equals(Object obj) {
 		if (this == obj) {

+ 4 - 16
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java

@@ -16,16 +16,14 @@
 
 package org.springframework.security.oauth2.oidc.core.user;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
 import org.springframework.security.oauth2.oidc.core.IdToken;
 import org.springframework.security.oauth2.oidc.core.IdTokenClaim;
 import org.springframework.security.oauth2.oidc.core.UserInfo;
-import org.springframework.util.Assert;
+
+import java.util.Map;
+import java.util.Set;
 
 /**
  * The default implementation of an {@link OidcUser}.
@@ -61,7 +59,7 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
 
 	public DefaultOidcUser(Set<GrantedAuthority> authorities, IdToken idToken, UserInfo userInfo,
 			String nameAttributeKey) {
-		super(authorities, resolveAttributes(idToken, userInfo), nameAttributeKey);
+		super(authorities, OidcUser.collectClaims(idToken, userInfo), nameAttributeKey);
 		this.idToken = idToken;
 		this.userInfo = userInfo;
 	}
@@ -78,14 +76,4 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
 	public UserInfo getUserInfo() {
 		return this.userInfo;
 	}
-
-	private static Map<String, Object> resolveAttributes(IdToken idToken, UserInfo userInfo) {
-		Assert.notNull(idToken, "idToken cannot be null");
-		Map<String, Object> attributes = new HashMap<>();
-		attributes.putAll(idToken.getClaims());
-		if (userInfo != null) {
-			attributes.putAll(userInfo.getClaims());
-		}
-		return attributes;
-	}
 }

+ 11 - 0
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java

@@ -22,7 +22,9 @@ import org.springframework.security.oauth2.oidc.core.IdToken;
 import org.springframework.security.oauth2.oidc.core.IdTokenClaimAccessor;
 import org.springframework.security.oauth2.oidc.core.StandardClaimAccessor;
 import org.springframework.security.oauth2.oidc.core.UserInfo;
+import org.springframework.util.Assert;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -54,4 +56,13 @@ public interface OidcUser extends OAuth2User, IdTokenClaimAccessor {
 
 	Map<String, Object> getClaims();
 
+	static Map<String, Object> collectClaims(IdToken idToken, UserInfo userInfo) {
+		Assert.notNull(idToken, "idToken cannot be null");
+		Map<String, Object> claims = new HashMap<>();
+		if (userInfo != null) {
+			claims.putAll(userInfo.getClaims());
+		}
+		claims.putAll(idToken.getClaims());
+		return claims;
+	}
 }

+ 1 - 12
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java

@@ -21,10 +21,6 @@ import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 import org.springframework.security.oauth2.oidc.core.IdToken;
 import org.springframework.security.oauth2.oidc.core.UserInfo;
 
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
 /**
  * A {@link GrantedAuthority} that is associated with an {@link OidcUser}.
  *
@@ -46,16 +42,9 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
 	}
 
 	public OidcUserAuthority(String authority, IdToken idToken, UserInfo userInfo) {
-		super(authority, idToken.getClaims());
+		super(authority, OidcUser.collectClaims(idToken, userInfo));
 		this.idToken = idToken;
 		this.userInfo = userInfo;
-		if (userInfo != null) {
-			this.setAttributes(
-				Stream.of(this.getAttributes(), userInfo.getClaims())
-					.flatMap(m -> m.entrySet().stream())
-					.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k1))
-			);
-		}
 	}
 
 	public IdToken getIdToken() {

+ 1 - 0
samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle

@@ -3,6 +3,7 @@ apply plugin: 'io.spring.convention.spring-sample-boot'
 dependencies {
 	compile project(':spring-security-config')
 	compile project(':spring-security-oauth2-client')
+	compile project(':spring-security-jwt-jose')
 	compile 'org.springframework:spring-webflux'
 	compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
 	compile 'org.springframework.boot:spring-boot-starter-web'