parent
71b1720cfe
commit
0e9b2807bf
|
@ -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));
|
||||
}
|
||||
this.userInfoService = nimbusOAuth2UserService;
|
||||
if (this.isOidcClientRegistered()) {
|
||||
oauth2UserServices.add(new OidcUserService());
|
||||
}
|
||||
if (!this.customUserTypes.isEmpty()) {
|
||||
oauth2UserServices.add(new CustomUserTypesOAuth2UserService(this.customUserTypes));
|
||||
}
|
||||
this.userInfoService = new DelegatingOAuth2UserService(oauth2UserServices);
|
||||
}
|
||||
return this.userInfoService;
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>"user's name"</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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 "user's name" 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue