parent
c986b6f4b5
commit
9cfb890207
|
@ -13,6 +13,7 @@ dependencies {
|
|||
optional project(':spring-security-ldap')
|
||||
optional project(':spring-security-messaging')
|
||||
optional project(':spring-security-oauth2-client')
|
||||
optional project(':spring-security-jwt-jose')
|
||||
optional project(':spring-security-openid')
|
||||
optional project(':spring-security-web')
|
||||
optional project(':spring-security-webflux')
|
||||
|
|
|
@ -15,25 +15,34 @@
|
|||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||
import org.springframework.security.jwt.JwtDecoder;
|
||||
import org.springframework.security.jwt.nimbus.NimbusJwtDecoderJwkSupport;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
|
||||
import org.springframework.security.oauth2.client.authentication.nimbus.NimbusAuthorizationCodeTokenExchanger;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.user.nimbus.NimbusOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.authentication.jwt.DefaultProviderJwtDecoderRegistry;
|
||||
import org.springframework.security.oauth2.core.provider.DefaultProviderMetadata;
|
||||
import org.springframework.security.oauth2.client.authentication.jwt.ProviderJwtDecoderRegistry;
|
||||
import org.springframework.security.oauth2.core.provider.ProviderMetadata;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
|
@ -43,7 +52,8 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
|
|||
|
||||
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
|
||||
private OAuth2UserService userInfoService;
|
||||
private Map<URI, Function<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters = new HashMap<>();
|
||||
private Map<URI, Class<? extends OAuth2User>> customUserTypes = new HashMap<>();
|
||||
private Map<URI, String> userNameAttributeNames = new HashMap<>();
|
||||
|
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer() {
|
||||
|
@ -71,10 +81,17 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
|
|||
return this;
|
||||
}
|
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> userInfoTypeConverter(Function<ClientHttpResponse, ? extends OAuth2User> userInfoConverter, URI userInfoUri) {
|
||||
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> customUserType(Class<? extends OAuth2User> customUserType, URI userInfoUri) {
|
||||
Assert.notNull(customUserType, "customUserType cannot be null");
|
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null");
|
||||
this.userInfoTypeConverters.put(userInfoUri, userInfoConverter);
|
||||
this.customUserTypes.put(userInfoUri, customUserType);
|
||||
return this;
|
||||
}
|
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> userNameAttributeName(String userNameAttributeName, URI userInfoUri) {
|
||||
Assert.hasText(userNameAttributeName, "userNameAttributeName cannot be empty");
|
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null");
|
||||
this.userNameAttributeNames.put(userInfoUri, userNameAttributeName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -89,7 +106,7 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
|
|||
@Override
|
||||
public void init(H http) throws Exception {
|
||||
AuthorizationCodeAuthenticationProvider authenticationProvider = new AuthorizationCodeAuthenticationProvider(
|
||||
this.getAuthorizationCodeTokenExchanger(), this.getUserInfoService());
|
||||
this.getAuthorizationCodeTokenExchanger(), this.getProviderJwtDecoderRegistry(), this.getUserInfoService());
|
||||
authenticationProvider = this.postProcess(authenticationProvider);
|
||||
http.authenticationProvider(authenticationProvider);
|
||||
super.init(http);
|
||||
|
@ -114,10 +131,51 @@ final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecuri
|
|||
return this.authorizationCodeTokenExchanger;
|
||||
}
|
||||
|
||||
private ProviderJwtDecoderRegistry getProviderJwtDecoderRegistry() {
|
||||
Map<ProviderMetadata, JwtDecoder> jwtDecoders = new HashMap<>();
|
||||
ClientRegistrationRepository clientRegistrationRepository = OAuth2LoginConfigurer.getClientRegistrationRepository(this.getBuilder());
|
||||
clientRegistrationRepository.getRegistrations().stream().forEach(registration -> {
|
||||
ClientRegistration.ProviderDetails providerDetails = registration.getProviderDetails();
|
||||
if (StringUtils.hasText(providerDetails.getJwkSetUri())) {
|
||||
DefaultProviderMetadata providerMetadata = new DefaultProviderMetadata();
|
||||
// Default the Issuer to the host of the Authorization Endpoint
|
||||
providerMetadata.setIssuer(this.toURL(
|
||||
UriComponentsBuilder
|
||||
.fromHttpUrl(providerDetails.getAuthorizationUri())
|
||||
.replacePath(null)
|
||||
.toUriString()
|
||||
));
|
||||
providerMetadata.setAuthorizationEndpoint(this.toURL(providerDetails.getAuthorizationUri()));
|
||||
providerMetadata.setTokenEndpoint(this.toURL(providerDetails.getTokenUri()));
|
||||
providerMetadata.setUserInfoEndpoint(this.toURL(providerDetails.getUserInfoUri()));
|
||||
providerMetadata.setJwkSetUri(this.toURL(providerDetails.getJwkSetUri()));
|
||||
jwtDecoders.put(providerMetadata, new NimbusJwtDecoderJwkSupport(providerDetails.getJwkSetUri()));
|
||||
}
|
||||
});
|
||||
return new DefaultProviderJwtDecoderRegistry(jwtDecoders);
|
||||
}
|
||||
|
||||
private OAuth2UserService getUserInfoService() {
|
||||
if (this.userInfoService == null) {
|
||||
this.userInfoService = new NimbusOAuth2UserService(this.userInfoTypeConverters);
|
||||
this.userInfoService = new NimbusOAuth2UserService();
|
||||
if (!this.customUserTypes.isEmpty()) {
|
||||
((NimbusOAuth2UserService)this.userInfoService).setCustomUserTypes(this.customUserTypes);
|
||||
}
|
||||
if (!this.userNameAttributeNames.isEmpty()) {
|
||||
((NimbusOAuth2UserService)this.userInfoService).setUserNameAttributeNames(this.userNameAttributeNames);
|
||||
}
|
||||
}
|
||||
return this.userInfoService;
|
||||
}
|
||||
|
||||
private URL toURL(String urlStr) {
|
||||
if (!StringUtils.hasText(urlStr)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new URL(urlStr);
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException("Failed to convert '" + urlStr + "' to a URL: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
|
||||
|
@ -35,7 +34,6 @@ import org.springframework.util.CollectionUtils;
|
|||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -95,10 +93,17 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
|||
return this.and();
|
||||
}
|
||||
|
||||
public OAuth2LoginConfigurer<B> userInfoTypeConverter(Function<ClientHttpResponse, ? extends OAuth2User> userInfoConverter, URI userInfoUri) {
|
||||
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
|
||||
public OAuth2LoginConfigurer<B> customUserType(Class<? extends OAuth2User> customUserType, URI userInfoUri) {
|
||||
Assert.notNull(customUserType, "customUserType cannot be null");
|
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null");
|
||||
OAuth2LoginConfigurer.this.authorizationCodeAuthenticationFilterConfigurer.userInfoTypeConverter(userInfoConverter, userInfoUri);
|
||||
OAuth2LoginConfigurer.this.authorizationCodeAuthenticationFilterConfigurer.customUserType(customUserType, userInfoUri);
|
||||
return this.and();
|
||||
}
|
||||
|
||||
public OAuth2LoginConfigurer<B> userNameAttributeName(String userNameAttributeName, URI userInfoUri) {
|
||||
Assert.hasText(userNameAttributeName, "userNameAttributeName cannot be empty");
|
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null");
|
||||
OAuth2LoginConfigurer.this.authorizationCodeAuthenticationFilterConfigurer.userNameAttributeName(userNameAttributeName, userInfoUri);
|
||||
return this.and();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ package org.springframework.security.jwt;
|
|||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
|
@ -33,17 +33,17 @@ import java.time.Instant;
|
|||
*/
|
||||
public interface JwtClaimAccessor extends ClaimAccessor {
|
||||
|
||||
default URI getIssuer() {
|
||||
return this.getClaimAsURI(JwtClaim.ISS);
|
||||
default URL getIssuer() {
|
||||
return this.getClaimAsURL(JwtClaim.ISS);
|
||||
}
|
||||
|
||||
default String getSubject() {
|
||||
return this.getClaimAsString(JwtClaim.SUB);
|
||||
}
|
||||
|
||||
default String getAudience() {
|
||||
// FIXME Should return String[]
|
||||
return this.getClaimAsString(JwtClaim.AUD);
|
||||
default String[] getAudience() {
|
||||
// TODO Impl JwtClaim.AUD
|
||||
return null;
|
||||
}
|
||||
|
||||
default Instant getExpiresAt() {
|
||||
|
|
|
@ -3,10 +3,11 @@ 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 'com.nimbusds:oauth2-oidc-sdk'
|
||||
compile 'org.springframework:spring-web'
|
||||
compile 'com.nimbusds:oauth2-oidc-sdk'
|
||||
|
||||
provided 'javax.servlet:javax.servlet-api'
|
||||
}
|
||||
|
|
|
@ -218,6 +218,7 @@ public class AuthorizationCodeAuthenticationProcessingFilter extends AbstractAut
|
|||
this.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri());
|
||||
this.tokenUri(clientRegistration.getProviderDetails().getTokenUri());
|
||||
this.userInfoUri(clientRegistration.getProviderDetails().getUserInfoUri());
|
||||
this.jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri());
|
||||
this.clientName(clientRegistration.getClientName());
|
||||
this.clientAlias(clientRegistration.getClientAlias());
|
||||
}
|
||||
|
|
|
@ -22,10 +22,16 @@ import org.springframework.security.core.AuthenticationException;
|
|||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||
import org.springframework.security.jwt.Jwt;
|
||||
import org.springframework.security.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.client.authentication.jwt.ProviderJwtDecoderRegistry;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AccessToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.core.IdToken;
|
||||
import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -68,16 +74,20 @@ import java.util.Collection;
|
|||
*/
|
||||
public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
|
||||
private final ProviderJwtDecoderRegistry providerJwtDecoderRegistry;
|
||||
private final OAuth2UserService userInfoService;
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
|
||||
|
||||
public AuthorizationCodeAuthenticationProvider(
|
||||
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
|
||||
ProviderJwtDecoderRegistry providerJwtDecoderRegistry,
|
||||
OAuth2UserService userInfoService) {
|
||||
|
||||
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
|
||||
Assert.notNull(providerJwtDecoderRegistry, "providerJwtDecoderRegistry cannot be null");
|
||||
Assert.notNull(userInfoService, "userInfoService cannot be null");
|
||||
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
|
||||
this.providerJwtDecoderRegistry = providerJwtDecoderRegistry;
|
||||
this.userInfoService = userInfoService;
|
||||
}
|
||||
|
||||
|
@ -85,6 +95,7 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
|
|||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||
(AuthorizationCodeAuthenticationToken) authentication;
|
||||
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
|
||||
|
||||
TokenResponseAttributes tokenResponse =
|
||||
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
|
||||
|
@ -92,8 +103,20 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
|
|||
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(),
|
||||
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
|
||||
tokenResponse.getExpiresAt(), tokenResponse.getScopes());
|
||||
OAuth2AuthenticationToken accessTokenAuthentication = new OAuth2AuthenticationToken(
|
||||
authorizationCodeAuthentication.getClientRegistration(), accessToken);
|
||||
|
||||
IdToken idToken = null;
|
||||
if (tokenResponse.getAdditionalParameters().containsKey(OidcParameter.ID_TOKEN)) {
|
||||
JwtDecoder jwtDecoder = this.providerJwtDecoderRegistry.getJwtDecoder(clientRegistration.getProviderDetails().getJwkSetUri());
|
||||
if (jwtDecoder == null) {
|
||||
throw new IllegalArgumentException("Unable to find a registered JwtDecoder for the provider '" + clientRegistration.getProviderDetails().getTokenUri() +
|
||||
"'. Check to ensure you have configured the JwkSet URI property.");
|
||||
}
|
||||
Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
|
||||
idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
||||
}
|
||||
|
||||
OAuth2AuthenticationToken accessTokenAuthentication =
|
||||
new OAuth2AuthenticationToken(clientRegistration, accessToken, idToken);
|
||||
accessTokenAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
|
||||
|
||||
OAuth2User user = this.userInfoService.loadUser(accessTokenAuthentication);
|
||||
|
@ -101,20 +124,21 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
|
|||
Collection<? extends GrantedAuthority> authorities =
|
||||
this.authoritiesMapper.mapAuthorities(user.getAuthorities());
|
||||
|
||||
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(user, authorities,
|
||||
accessTokenAuthentication.getClientRegistration(), accessTokenAuthentication.getAccessToken());
|
||||
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(
|
||||
user, authorities, accessTokenAuthentication.getClientRegistration(),
|
||||
accessTokenAuthentication.getAccessToken(), accessTokenAuthentication.getIdToken());
|
||||
authenticationResult.setDetails(accessTokenAuthentication.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
|||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AccessToken;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.core.IdToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -33,7 +34,7 @@ import java.util.Collection;
|
|||
* that represents an <i>OAuth 2.0</i> {@link Authentication}.
|
||||
*
|
||||
* <p>
|
||||
* It associates an {@link OAuth2User}, {@link ClientRegistration} and an {@link AccessToken}.
|
||||
* It associates an {@link OAuth2User}, {@link ClientRegistration}, {@link AccessToken} and optionally an {@link IdToken}.
|
||||
* This <code>Authentication</code> is considered <i>"authenticated"</i> if the {@link OAuth2User}
|
||||
* is provided in the respective constructor. This typically happens after the {@link OAuth2UserService}
|
||||
* retrieves the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>.
|
||||
|
@ -43,19 +44,21 @@ import java.util.Collection;
|
|||
* @see OAuth2User
|
||||
* @see ClientRegistration
|
||||
* @see AccessToken
|
||||
* @see IdToken
|
||||
*/
|
||||
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final OAuth2User principal;
|
||||
private final ClientRegistration clientRegistration;
|
||||
private final AccessToken accessToken;
|
||||
private final IdToken idToken;
|
||||
|
||||
public OAuth2AuthenticationToken(ClientRegistration clientRegistration, AccessToken accessToken) {
|
||||
this(null, AuthorityUtils.NO_AUTHORITIES, clientRegistration, accessToken);
|
||||
public OAuth2AuthenticationToken(ClientRegistration clientRegistration, AccessToken accessToken, IdToken idToken) {
|
||||
this(null, AuthorityUtils.NO_AUTHORITIES, clientRegistration, accessToken, idToken);
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities,
|
||||
ClientRegistration clientRegistration, AccessToken accessToken) {
|
||||
ClientRegistration clientRegistration, AccessToken accessToken, IdToken idToken) {
|
||||
|
||||
super(authorities);
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
|
@ -63,6 +66,7 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
|||
this.principal = principal;
|
||||
this.clientRegistration = clientRegistration;
|
||||
this.accessToken = accessToken;
|
||||
this.idToken = idToken;
|
||||
this.setAuthenticated(principal != null);
|
||||
}
|
||||
|
||||
|
@ -84,4 +88,8 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
|||
public AccessToken getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public IdToken getIdToken() {
|
||||
return this.idToken;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.authentication.jwt;
|
||||
|
||||
import org.springframework.security.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.core.provider.ProviderMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The default implementation of a {@link ProviderJwtDecoderRegistry} that associates
|
||||
* a {@link JwtDecoder} to a {@link ProviderMetadata}. The <code>ProviderMetadata</code>
|
||||
* is matched against the <code>providerIdentifier</code> parameter passed to {@link #getJwtDecoder(String)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DefaultProviderJwtDecoderRegistry implements ProviderJwtDecoderRegistry {
|
||||
private final Map<ProviderMetadata, JwtDecoder> jwtDecoders;
|
||||
|
||||
public DefaultProviderJwtDecoderRegistry(Map<ProviderMetadata, JwtDecoder> jwtDecoders) {
|
||||
Assert.notNull(jwtDecoders, "jwtDecoders cannot be null");
|
||||
this.jwtDecoders = Collections.unmodifiableMap(new HashMap<>(jwtDecoders));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtDecoder getJwtDecoder(String providerIdentifier) {
|
||||
Assert.hasText(providerIdentifier, "providerIdentifier cannot be empty");
|
||||
Optional<ProviderMetadata> providerMetadataKey = this.jwtDecoders.keySet().stream().filter(providerMetadata ->
|
||||
providerIdentifier.equals(providerMetadata.getIssuer().toString()) ||
|
||||
providerIdentifier.equals(providerMetadata.getAuthorizationEndpoint().toString()) ||
|
||||
providerIdentifier.equals(providerMetadata.getTokenEndpoint().toString()) ||
|
||||
providerIdentifier.equals(providerMetadata.getUserInfoEndpoint().toString()) ||
|
||||
providerIdentifier.equals(providerMetadata.getJwkSetUri().toString())
|
||||
).findFirst();
|
||||
return (providerMetadataKey.isPresent() ? this.jwtDecoders.get(providerMetadataKey.get()) : null);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.authentication.jwt;
|
||||
|
||||
import org.springframework.security.jwt.JwtDecoder;
|
||||
|
||||
/**
|
||||
* Support classes for converting to {@link org.springframework.security.oauth2.core.user.OAuth2User}.
|
||||
* A registry for {@link JwtDecoder}'s that are associated to an <i>OAuth 2.0 Provider</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.user.converter;
|
||||
public interface ProviderJwtDecoderRegistry {
|
||||
|
||||
JwtDecoder getJwtDecoder(String providerIdentifier);
|
||||
|
||||
}
|
|
@ -122,6 +122,7 @@ public class ClientRegistration {
|
|||
private String authorizationUri;
|
||||
private String tokenUri;
|
||||
private String userInfoUri;
|
||||
private String jwkSetUri;
|
||||
|
||||
protected ProviderDetails() {
|
||||
}
|
||||
|
@ -149,6 +150,14 @@ public class ClientRegistration {
|
|||
protected void setUserInfoUri(String userInfoUri) {
|
||||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
|
||||
public String getJwkSetUri() {
|
||||
return this.jwkSetUri;
|
||||
}
|
||||
|
||||
protected void setJwkSetUri(String jwkSetUri) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -161,6 +170,7 @@ public class ClientRegistration {
|
|||
protected String authorizationUri;
|
||||
protected String tokenUri;
|
||||
protected String userInfoUri;
|
||||
protected String jwkSetUri;
|
||||
protected String clientName;
|
||||
protected String clientAlias;
|
||||
|
||||
|
@ -180,6 +190,7 @@ public class ClientRegistration {
|
|||
this.authorizationUri(clientRegistrationProperties.getAuthorizationUri());
|
||||
this.tokenUri(clientRegistrationProperties.getTokenUri());
|
||||
this.userInfoUri(clientRegistrationProperties.getUserInfoUri());
|
||||
this.jwkSetUri(clientRegistrationProperties.getJwkSetUri());
|
||||
this.clientName(clientRegistrationProperties.getClientName());
|
||||
this.clientAlias(clientRegistrationProperties.getClientAlias());
|
||||
}
|
||||
|
@ -227,6 +238,11 @@ public class ClientRegistration {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder jwkSetUri(String jwkSetUri) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
return this;
|
||||
|
@ -256,6 +272,7 @@ public class ClientRegistration {
|
|||
providerDetails.setAuthorizationUri(this.authorizationUri);
|
||||
providerDetails.setTokenUri(this.tokenUri);
|
||||
providerDetails.setUserInfoUri(this.userInfoUri);
|
||||
providerDetails.setJwkSetUri(this.jwkSetUri);
|
||||
clientRegistration.setProviderDetails(providerDetails);
|
||||
|
||||
clientRegistration.setClientName(this.clientName);
|
||||
|
|
|
@ -42,6 +42,7 @@ public class ClientRegistrationProperties {
|
|||
private String authorizationUri;
|
||||
private String tokenUri;
|
||||
private String userInfoUri;
|
||||
private String jwkSetUri;
|
||||
private String clientName;
|
||||
private String clientAlias;
|
||||
|
||||
|
@ -118,6 +119,14 @@ public class ClientRegistrationProperties {
|
|||
this.userInfoUri = userInfoUri;
|
||||
}
|
||||
|
||||
public String getJwkSetUri() {
|
||||
return this.jwkSetUri;
|
||||
}
|
||||
|
||||
public void setJwkSetUri(String jwkSetUri) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return this.clientName;
|
||||
}
|
||||
|
|
|
@ -19,20 +19,21 @@ import org.springframework.security.core.AuthenticatedPrincipal;
|
|||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.user.UserInfo;
|
||||
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>
|
||||
* using the provided {@link OAuth2AuthenticationToken#getAccessToken()}
|
||||
* and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}
|
||||
* (for a standard <i>OAuth 2.0 Provider</i>) or {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>).
|
||||
* and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2AuthenticationToken
|
||||
* @see AuthenticatedPrincipal
|
||||
* @see OAuth2User
|
||||
* @see OidcUser
|
||||
* @see UserInfo
|
||||
*/
|
||||
public interface OAuth2UserService {
|
||||
|
|
|
@ -1,57 +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.converter;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Base implementation of a <code>Function</code> that converts a {@link ClientHttpResponse}
|
||||
* to a specific type of {@link OAuth2User}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2User
|
||||
* @see ClientHttpResponse
|
||||
*/
|
||||
public abstract class AbstractOAuth2UserConverter<R extends OAuth2User> implements Function<ClientHttpResponse, R> {
|
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
protected AbstractOAuth2UserConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final R apply(ClientHttpResponse clientHttpResponse) {
|
||||
Map<String, Object> userAttributes;
|
||||
|
||||
try {
|
||||
userAttributes = (Map<String, Object>) this.jackson2HttpMessageConverter.read(Map.class, clientHttpResponse);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return this.apply(userAttributes);
|
||||
}
|
||||
|
||||
protected abstract R apply(Map<String, Object> userAttributes);
|
||||
|
||||
}
|
|
@ -1,57 +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.converter;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A <code>Function</code> that converts a {@link ClientHttpResponse}
|
||||
* to a custom type of {@link OAuth2User}, as supplied via the constructor.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2User
|
||||
* @see ClientHttpResponse
|
||||
*/
|
||||
public final class CustomOAuth2UserConverter<R extends OAuth2User> implements Function<ClientHttpResponse, R> {
|
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
private final Class<R> customType;
|
||||
|
||||
public CustomOAuth2UserConverter(Class<R> customType) {
|
||||
Assert.notNull(customType, "customType cannot be null");
|
||||
this.customType = customType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(ClientHttpResponse clientHttpResponse) {
|
||||
R user;
|
||||
|
||||
try {
|
||||
user = (R) this.jackson2HttpMessageConverter.read(this.customType, clientHttpResponse);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -1,46 +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.converter;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link AbstractOAuth2UserConverter} that converts
|
||||
* a {@link ClientHttpResponse} to a {@link OAuth2User}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2User
|
||||
* @see ClientHttpResponse
|
||||
*/
|
||||
public final class OAuth2UserConverter extends AbstractOAuth2UserConverter<OAuth2User> {
|
||||
private final String nameAttributeKey;
|
||||
|
||||
public OAuth2UserConverter(String nameAttributeKey) {
|
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
|
||||
this.nameAttributeKey = nameAttributeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OAuth2User apply(Map<String, Object> userAttributes) {
|
||||
return new DefaultOAuth2User(userAttributes, this.nameAttributeKey);
|
||||
}
|
||||
}
|
|
@ -22,112 +22,206 @@ 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.MediaType;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
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.oidc.user.UserInfo;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2UserService} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
|
||||
*
|
||||
* <p>
|
||||
* This implementation uses a <code>Map</code> of converter's <i>keyed</i> by <code>URI</code>.
|
||||
* The <code>URI</code> represents the <i>UserInfo Endpoint</i> address and the mapped <code>Function</code>
|
||||
* is capable of converting the <i>UserInfo Response</i> to either an
|
||||
* {@link OAuth2User} (for a standard <i>OAuth 2.0 Provider</i>) or
|
||||
* {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>).
|
||||
* 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 OAuth2AuthenticationToken
|
||||
* @see AuthenticatedPrincipal
|
||||
* @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 Map<URI, Function<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters;
|
||||
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(Map<URI, Function<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters) {
|
||||
Assert.notEmpty(userInfoTypeConverters, "userInfoTypeConverters cannot be empty");
|
||||
this.userInfoTypeConverters = new HashMap<>(userInfoTypeConverters);
|
||||
public NimbusOAuth2UserService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException {
|
||||
OAuth2User user;
|
||||
public final OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException {
|
||||
URI userInfoUri = this.getUserInfoUri(token);
|
||||
|
||||
try {
|
||||
ClientRegistration clientRegistration = token.getClientRegistration();
|
||||
|
||||
URI userInfoUri;
|
||||
try {
|
||||
userInfoUri = new URI(clientRegistration.getProviderDetails().getUserInfoUri());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("An error occurred parsing the userInfo URI: " +
|
||||
clientRegistration.getProviderDetails().getUserInfoUri(), ex);
|
||||
}
|
||||
|
||||
Function<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = this.userInfoTypeConverters.get(userInfoUri);
|
||||
if (userInfoConverter == null) {
|
||||
throw new IllegalArgumentException("There is no available User Info converter for " + userInfoUri.toString());
|
||||
}
|
||||
|
||||
BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().getTokenValue());
|
||||
|
||||
// Request the User Info
|
||||
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
|
||||
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
HTTPResponse httpResponse = httpRequest.send();
|
||||
|
||||
if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) {
|
||||
UserInfoErrorResponse userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse);
|
||||
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());
|
||||
}
|
||||
|
||||
user = userInfoConverter.apply(new NimbusClientHttpResponse(httpResponse));
|
||||
|
||||
} catch (ParseException ex) {
|
||||
// This error occurs if the User Info Response is not well-formed or invalid
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE), ex);
|
||||
} catch (IOException ex) {
|
||||
// This error occurs when there is a network-related issue
|
||||
throw new AuthenticationServiceException("An error occurred while sending the User Info Request: " +
|
||||
ex.getMessage(), ex);
|
||||
if (this.getCustomUserTypes().containsKey(userInfoUri)) {
|
||||
return this.loadCustomUser(token);
|
||||
}
|
||||
if (token.getIdToken() != null) {
|
||||
return this.loadOidcUser(token);
|
||||
}
|
||||
|
||||
return this.loadOAuth2User(token);
|
||||
}
|
||||
|
||||
protected OAuth2User loadOidcUser(OAuth2AuthenticationToken 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(OAuth2AuthenticationToken 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(OAuth2AuthenticationToken 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 (token.getIdToken() != null) {
|
||||
userAttributes.putAll(token.getIdToken().getClaims());
|
||||
}
|
||||
|
||||
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(user);
|
||||
wrapper.setAutoGrowNestedPaths(true);
|
||||
wrapper.setPropertyValues(userAttributes);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
protected Map<String, Object> getUserInfo(OAuth2AuthenticationToken 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.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
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(OAuth2AuthenticationToken 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,4 +56,32 @@ public abstract class AbstractToken implements Serializable {
|
|||
public Instant getExpiresAt() {
|
||||
return this.expiresAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractToken that = (AbstractToken) obj;
|
||||
|
||||
if (!this.getTokenValue().equals(that.getTokenValue())) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getIssuedAt().equals(that.getIssuedAt())) {
|
||||
return false;
|
||||
}
|
||||
return this.getExpiresAt().equals(that.getExpiresAt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.getTokenValue().hashCode();
|
||||
result = 31 * result + this.getIssuedAt().hashCode();
|
||||
result = 31 * result + this.getExpiresAt().hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ package org.springframework.security.oauth2.core;
|
|||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -56,14 +56,14 @@ public interface ClaimAccessor {
|
|||
}
|
||||
}
|
||||
|
||||
default URI getClaimAsURI(String claim) {
|
||||
default URL getClaimAsURL(String claim) {
|
||||
if (!this.containsClaim(claim)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new URI(this.getClaimAsString(claim));
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new IllegalArgumentException("Unable to convert claim '" + claim + "' to URI: " + ex.getMessage(), ex);
|
||||
return new URL(this.getClaimAsString(claim));
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException("Unable to convert claim '" + claim + "' to URL: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.core.provider;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ProviderMetadata}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DefaultProviderMetadata implements ProviderMetadata {
|
||||
private URL issuer;
|
||||
private URL authorizationEndpoint;
|
||||
private URL tokenEndpoint;
|
||||
private URL userInfoEndpoint;
|
||||
private URL jwkSetUri;
|
||||
|
||||
public DefaultProviderMetadata() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
public void setIssuer(URL issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getAuthorizationEndpoint() {
|
||||
return authorizationEndpoint;
|
||||
}
|
||||
|
||||
public void setAuthorizationEndpoint(URL authorizationEndpoint) {
|
||||
this.authorizationEndpoint = authorizationEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getTokenEndpoint() {
|
||||
return tokenEndpoint;
|
||||
}
|
||||
|
||||
public void setTokenEndpoint(URL tokenEndpoint) {
|
||||
this.tokenEndpoint = tokenEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUserInfoEndpoint() {
|
||||
return userInfoEndpoint;
|
||||
}
|
||||
|
||||
public void setUserInfoEndpoint(URL userInfoEndpoint) {
|
||||
this.userInfoEndpoint = userInfoEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getJwkSetUri() {
|
||||
return jwkSetUri;
|
||||
}
|
||||
|
||||
public void setJwkSetUri(URL jwkSetUri) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DefaultProviderMetadata that = (DefaultProviderMetadata) obj;
|
||||
|
||||
if (!this.getIssuer().equals(that.getIssuer())) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getAuthorizationEndpoint().equals(that.getAuthorizationEndpoint())) {
|
||||
return false;
|
||||
}
|
||||
if (!this.getTokenEndpoint().equals(that.getTokenEndpoint())) {
|
||||
return false;
|
||||
}
|
||||
return this.getUserInfoEndpoint().equals(that.getUserInfoEndpoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.getIssuer().hashCode();
|
||||
result = 31 * result + this.getAuthorizationEndpoint().hashCode();
|
||||
result = 31 * result + this.getTokenEndpoint().hashCode();
|
||||
result = 31 * result + this.getUserInfoEndpoint().hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -13,27 +13,26 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.user.converter;
|
||||
package org.springframework.security.oauth2.core.provider;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.oauth2.oidc.user.DefaultUserInfo;
|
||||
import org.springframework.security.oauth2.oidc.user.UserInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link AbstractOAuth2UserConverter} that converts
|
||||
* a {@link ClientHttpResponse} to a {@link UserInfo}.
|
||||
* Metadata describing the configuration information for an <i>OAuth 2.0 Provider</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see UserInfo
|
||||
* @see ClientHttpResponse
|
||||
*/
|
||||
public final class UserInfoConverter extends AbstractOAuth2UserConverter<UserInfo> {
|
||||
public interface ProviderMetadata {
|
||||
|
||||
URL getIssuer();
|
||||
|
||||
URL getAuthorizationEndpoint();
|
||||
|
||||
URL getTokenEndpoint();
|
||||
|
||||
URL getUserInfoEndpoint();
|
||||
|
||||
URL getJwkSetUri();
|
||||
|
||||
@Override
|
||||
protected UserInfo apply(Map<String, Object> userAttributes) {
|
||||
return new DefaultUserInfo(userAttributes);
|
||||
}
|
||||
}
|
|
@ -18,9 +18,7 @@ package org.springframework.security.oauth2.core.user;
|
|||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -42,22 +40,18 @@ import java.util.stream.Collectors;
|
|||
public class DefaultOAuth2User implements OAuth2User {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final Set<GrantedAuthority> authorities;
|
||||
private final Map<String, Object> attributes;
|
||||
private Map<String, Object> attributes;
|
||||
private final String nameAttributeKey;
|
||||
|
||||
public DefaultOAuth2User(Map<String, Object> attributes, String nameAttributeKey) {
|
||||
this(Collections.emptySet(), attributes, nameAttributeKey);
|
||||
}
|
||||
|
||||
public DefaultOAuth2User(Set<GrantedAuthority> authorities, Map<String, Object> attributes, String nameAttributeKey) {
|
||||
Assert.notNull(authorities, "authorities cannot be null");
|
||||
Assert.notEmpty(authorities, "authorities cannot be empty");
|
||||
Assert.notEmpty(attributes, "attributes cannot be empty");
|
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
|
||||
if (!attributes.containsKey(nameAttributeKey)) {
|
||||
throw new IllegalArgumentException("Invalid nameAttributeKey: " + nameAttributeKey);
|
||||
}
|
||||
this.authorities = Collections.unmodifiableSet(this.sortAuthorities(authorities));
|
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
|
||||
this.setAttributes(attributes);
|
||||
this.nameAttributeKey = nameAttributeKey;
|
||||
}
|
||||
|
||||
|
@ -76,37 +70,15 @@ public class DefaultOAuth2User implements OAuth2User {
|
|||
return this.attributes;
|
||||
}
|
||||
|
||||
protected String getAttributeAsString(String key) {
|
||||
Object value = this.getAttributes().get(key);
|
||||
return (value != null ? value.toString() : null);
|
||||
}
|
||||
|
||||
protected Boolean getAttributeAsBoolean(String key) {
|
||||
String value = this.getAttributeAsString(key);
|
||||
return (value != null ? Boolean.valueOf(value) : null);
|
||||
}
|
||||
|
||||
protected Instant getAttributeAsInstant(String key) {
|
||||
String value = this.getAttributeAsString(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Instant.ofEpochSecond(Long.valueOf(value));
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Invalid long value: " + ex.getMessage(), ex);
|
||||
}
|
||||
protected final void setAttributes(Map<String, Object> attributes) {
|
||||
Assert.notEmpty(attributes, "attributes cannot be empty");
|
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> sortAuthorities(Set<GrantedAuthority> authorities) {
|
||||
if (CollectionUtils.isEmpty(authorities)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
SortedSet<GrantedAuthority> sortedAuthorities =
|
||||
new TreeSet<>((g1, g2) -> g1.getAuthority().compareTo(g2.getAuthority()));
|
||||
authorities.stream().forEach(sortedAuthorities::add);
|
||||
|
||||
return sortedAuthorities;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,4 +52,5 @@ public interface OAuth2User extends AuthenticatedPrincipal, Serializable {
|
|||
Collection<? extends GrantedAuthority> getAuthorities();
|
||||
|
||||
Map<String, Object> getAttributes();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.core.user;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link GrantedAuthority} that is associated with an {@link OAuth2User}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OAuth2User
|
||||
*/
|
||||
public class OAuth2UserAuthority implements GrantedAuthority {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final String authority;
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
public OAuth2UserAuthority(Map<String, Object> attributes) {
|
||||
this("ROLE_USER", attributes);
|
||||
}
|
||||
|
||||
public OAuth2UserAuthority(String authority, Map<String, Object> attributes) {
|
||||
Assert.hasText(authority, "authority cannot be empty");
|
||||
Assert.notEmpty(attributes, "attributes cannot be empty");
|
||||
this.authority = authority;
|
||||
this.setAttributes(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return this.authority;
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OAuth2UserAuthority that = (OAuth2UserAuthority) obj;
|
||||
|
||||
if (!this.getAuthority().equals(that.getAuthority())) {
|
||||
return false;
|
||||
}
|
||||
return this.getAttributes().equals(that.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.getAuthority().hashCode();
|
||||
result = 31 * result + this.getAttributes().hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAuthority();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
/**
|
||||
* The Address Claim represents a physical mailing address defined by the <i>OpenID Connect Core 1.0</i> specification
|
||||
* that can be returned either in the <i>UserInfo Response</i> or the <i>ID Token</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#AddressClaim">Address Claim</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse">UserInfo Response</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
*/
|
||||
public interface Address {
|
||||
|
||||
String getFormatted();
|
||||
|
||||
String getStreetAddress();
|
||||
|
||||
String getLocality();
|
||||
|
||||
String getRegion();
|
||||
|
||||
String getPostalCode();
|
||||
|
||||
String getCountry();
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.security.oauth2.core.AbstractToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractToken} representing an <i>OpenID Connect Core 1.0 ID Token</i>.
|
||||
*
|
||||
* <p>
|
||||
* The <code>IdToken</code> is a security token that contains "Claims"
|
||||
* about the authentication of an End-User by an Authorization Server.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AbstractToken
|
||||
* @see IdTokenClaimAccessor
|
||||
* @see StandardClaimAccessor
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a>
|
||||
*/
|
||||
public class IdToken extends AbstractToken implements IdTokenClaimAccessor {
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
public IdToken(String tokenValue, Instant issuedAt, Instant expiresAt, Map<String, Object> claims) {
|
||||
super(tokenValue, issuedAt, expiresAt);
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
/**
|
||||
* The "Claims" defined by the <i>OpenID Connect Core 1.0</i> specification
|
||||
* that can be returned in the <i>ID Token</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see IdToken
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
*/
|
||||
|
||||
public interface IdTokenClaim {
|
||||
|
||||
String ISS = "iss";
|
||||
|
||||
String SUB = "sub";
|
||||
|
||||
String AUD = "aud";
|
||||
|
||||
String EXP = "exp";
|
||||
|
||||
String IAT = "iat";
|
||||
|
||||
String AUTH_TIME = "auth_time";
|
||||
|
||||
String NONCE = "nonce";
|
||||
|
||||
String ACR = "acr";
|
||||
|
||||
String AMR = "amr";
|
||||
|
||||
String AZP = "azp";
|
||||
|
||||
String AT_HASH = "at_hash";
|
||||
|
||||
String C_HASH = "c_hash";
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "Claims" that can be returned in the <i>ID Token</i>
|
||||
* which provides information about the authentication of an End-User by an Authorization Server.
|
||||
*
|
||||
* @see ClaimAccessor
|
||||
* @see StandardClaimAccessor
|
||||
* @see StandardClaim
|
||||
* @see IdTokenClaim
|
||||
* @see IdToken
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a>
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface IdTokenClaimAccessor extends StandardClaimAccessor {
|
||||
|
||||
default URL getIssuer() {
|
||||
return this.getClaimAsURL(IdTokenClaim.ISS);
|
||||
}
|
||||
|
||||
default String getSubject() {
|
||||
return this.getClaimAsString(IdTokenClaim.SUB);
|
||||
}
|
||||
|
||||
default String[] getAudience() {
|
||||
// TODO Impl IdTokenClaim.AUD
|
||||
return null;
|
||||
}
|
||||
|
||||
default Instant getExpiresAt() {
|
||||
return this.getClaimAsInstant(IdTokenClaim.EXP);
|
||||
}
|
||||
|
||||
default Instant getIssuedAt() {
|
||||
return this.getClaimAsInstant(IdTokenClaim.IAT);
|
||||
}
|
||||
|
||||
default Instant getAuthenticatedAt() {
|
||||
return this.getClaimAsInstant(IdTokenClaim.AUTH_TIME);
|
||||
}
|
||||
|
||||
default String getNonce() {
|
||||
return this.getClaimAsString(IdTokenClaim.NONCE);
|
||||
}
|
||||
|
||||
default String getAuthenticationContextClass() {
|
||||
return this.getClaimAsString(IdTokenClaim.ACR);
|
||||
}
|
||||
|
||||
default String[] getAuthenticationMethods() {
|
||||
// TODO Impl IdTokenClaim.AMR
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getAuthorizedParty() {
|
||||
return this.getClaimAsString(IdTokenClaim.AZP);
|
||||
}
|
||||
|
||||
default String getAccessTokenHash() {
|
||||
return this.getClaimAsString(IdTokenClaim.AT_HASH);
|
||||
}
|
||||
|
||||
default String getAuthorizationCodeHash() {
|
||||
return this.getClaimAsString(IdTokenClaim.C_HASH);
|
||||
}
|
||||
}
|
|
@ -13,11 +13,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.oidc;
|
||||
package org.springframework.security.oauth2.oidc.core;
|
||||
|
||||
/**
|
||||
* The Standard Claims defined by the <i>OpenID Connect Core 1.0</i> specification
|
||||
* and returned in either the <i>UserInfo Response</i> or in the <i>ID Token</i>.
|
||||
* The "Standard Claims" defined by the <i>OpenID Connect Core 1.0</i> specification
|
||||
* that can be returned either in the <i>UserInfo Response</i> or the <i>ID Token</i>.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
|
@ -25,7 +25,7 @@ package org.springframework.security.oauth2.oidc;
|
|||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse">UserInfo Response</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
*/
|
||||
public interface StandardClaimName {
|
||||
public interface StandardClaim {
|
||||
|
||||
String SUB = "sub";
|
||||
|
||||
|
@ -66,4 +66,5 @@ public interface StandardClaimName {
|
|||
String ADDRESS = "address";
|
||||
|
||||
String UPDATED_AT = "updated_at";
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "Standard Claims" that can be returned
|
||||
* either in the <i>UserInfo Response</i> or the <i>ID Token</i>.
|
||||
*
|
||||
* @see ClaimAccessor
|
||||
* @see StandardClaim
|
||||
* @see UserInfo
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse">UserInfo Response</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a>
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface StandardClaimAccessor extends ClaimAccessor {
|
||||
|
||||
default String getSubject() {
|
||||
return this.getClaimAsString(StandardClaim.SUB);
|
||||
}
|
||||
|
||||
default String getFullName() {
|
||||
return this.getClaimAsString(StandardClaim.NAME);
|
||||
}
|
||||
|
||||
default String getGivenName() {
|
||||
return this.getClaimAsString(StandardClaim.GIVEN_NAME);
|
||||
}
|
||||
|
||||
default String getFamilyName() {
|
||||
return this.getClaimAsString(StandardClaim.FAMILY_NAME);
|
||||
}
|
||||
|
||||
default String getMiddleName() {
|
||||
return this.getClaimAsString(StandardClaim.MIDDLE_NAME);
|
||||
}
|
||||
|
||||
default String getNickName() {
|
||||
return this.getClaimAsString(StandardClaim.NICKNAME);
|
||||
}
|
||||
|
||||
default String getPreferredUsername() {
|
||||
return this.getClaimAsString(StandardClaim.PREFERRED_USERNAME);
|
||||
}
|
||||
|
||||
default String getProfile() {
|
||||
return this.getClaimAsString(StandardClaim.PROFILE);
|
||||
}
|
||||
|
||||
default String getPicture() {
|
||||
return this.getClaimAsString(StandardClaim.PICTURE);
|
||||
}
|
||||
|
||||
default String getWebsite() {
|
||||
return this.getClaimAsString(StandardClaim.WEBSITE);
|
||||
}
|
||||
|
||||
default String getEmail() {
|
||||
return this.getClaimAsString(StandardClaim.EMAIL);
|
||||
}
|
||||
|
||||
default Boolean getEmailVerified() {
|
||||
return this.getClaimAsBoolean(StandardClaim.EMAIL_VERIFIED);
|
||||
}
|
||||
|
||||
default String getGender() {
|
||||
return this.getClaimAsString(StandardClaim.GENDER);
|
||||
}
|
||||
|
||||
default String getBirthdate() {
|
||||
return this.getClaimAsString(StandardClaim.BIRTHDATE);
|
||||
}
|
||||
|
||||
default String getZoneInfo() {
|
||||
return this.getClaimAsString(StandardClaim.ZONEINFO);
|
||||
}
|
||||
|
||||
default String getLocale() {
|
||||
return this.getClaimAsString(StandardClaim.LOCALE);
|
||||
}
|
||||
|
||||
default String getPhoneNumber() {
|
||||
return this.getClaimAsString(StandardClaim.PHONE_NUMBER);
|
||||
}
|
||||
|
||||
default Boolean getPhoneNumberVerified() {
|
||||
return this.getClaimAsBoolean(StandardClaim.PHONE_NUMBER_VERIFIED);
|
||||
}
|
||||
|
||||
default Address getAddress() {
|
||||
// TODO Impl StandardClaim.ADDRESS
|
||||
return null;
|
||||
}
|
||||
|
||||
default Instant getUpdatedAt() {
|
||||
return this.getClaimAsInstant(StandardClaim.UPDATED_AT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A representation of a <i>UserInfo Response</i> that is returned
|
||||
* from the OAuth 2.0 Protected Resource <i>UserInfo Endpoint</i>.
|
||||
*
|
||||
* <p>
|
||||
* The <code>UserInfo</code> contains a set of "Standard Claims" about the authentication of an End-User.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see StandardClaimAccessor
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse">UserInfo Response</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfo">UserInfo Endpoint</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a>
|
||||
*/
|
||||
public class UserInfo implements StandardClaimAccessor {
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
public UserInfo(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserInfo that = (UserInfo) obj;
|
||||
|
||||
return this.getClaims().equals(that.getClaims());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getClaims().hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.core.endpoint;
|
||||
|
||||
/**
|
||||
* Standard parameters defined in the OAuth Parameters Registry
|
||||
* and used by the authorization endpoint and token endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#OAuthParametersRegistry">18.2 OAuth Parameters Registration</a>
|
||||
*/
|
||||
public interface OidcParameter {
|
||||
|
||||
String ID_TOKEN = "id_token";
|
||||
|
||||
}
|
|
@ -16,4 +16,4 @@
|
|||
/**
|
||||
* Core classes and interfaces providing support for <i>OpenID Connect Core 1.0</i>.
|
||||
*/
|
||||
package org.springframework.security.oauth2.oidc;
|
||||
package org.springframework.security.oauth2.oidc.core;
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.core.user;
|
||||
|
||||
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.StandardClaim;
|
||||
import org.springframework.security.oauth2.oidc.core.UserInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.springframework.security.oauth2.oidc.core.StandardClaim.NAME;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OidcUser}.
|
||||
*
|
||||
* <p>
|
||||
* The claim used for accessing the "name" of the
|
||||
* user <code>Principal</code> via {@link #getClaims()}
|
||||
* is {@link StandardClaim#NAME} or if not available
|
||||
* will default to {@link IdTokenClaim#SUB}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OidcUser
|
||||
* @see DefaultOAuth2User
|
||||
* @see IdToken
|
||||
* @see UserInfo
|
||||
*/
|
||||
public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
|
||||
private final IdToken idToken;
|
||||
private final UserInfo userInfo;
|
||||
|
||||
public DefaultOidcUser(Set<GrantedAuthority> authorities, IdToken idToken) {
|
||||
this(authorities, idToken, null);
|
||||
}
|
||||
|
||||
public DefaultOidcUser(Set<GrantedAuthority> authorities, IdToken idToken, UserInfo userInfo) {
|
||||
super(authorities, idToken.getClaims(), IdTokenClaim.SUB);
|
||||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String name = this.getClaimAsString(NAME);
|
||||
return (name != null ? name : super.getName());
|
||||
}
|
||||
}
|
|
@ -13,24 +13,25 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.oidc.user;
|
||||
package org.springframework.security.oauth2.oidc.core.user;
|
||||
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
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 java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A representation of a user <code>Principal</code>
|
||||
* that is registered with an <i>OpenID Connect 1.0 Provider</i>.
|
||||
*
|
||||
* <p>
|
||||
* The structure of the user <code>Principal</code> is defined by the
|
||||
* <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfo">UserInfo Endpoint</a>,
|
||||
* which is an <i>OAuth 2.0 Protected Resource</i> that returns a set of
|
||||
* <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Claims</a>
|
||||
* about the authenticated End-User.
|
||||
* An <code>OidcUser</code> contains "Claims" about the Authentication of the End-User.
|
||||
* The claims are aggregated from the <code>IdToken</code> and optionally the <code>UserInfo</code>.
|
||||
*
|
||||
* <p>
|
||||
* Implementation instances of this interface represent an {@link AuthenticatedPrincipal}
|
||||
|
@ -39,65 +40,18 @@ import java.time.Instant;
|
|||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see DefaultUserInfo
|
||||
* @see AuthenticatedPrincipal
|
||||
* @see DefaultOidcUser
|
||||
* @see OAuth2User
|
||||
* @see IdToken
|
||||
* @see UserInfo
|
||||
* @see IdTokenClaimAccessor
|
||||
* @see StandardClaimAccessor
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect Core 1.0</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfo">UserInfo Endpoint</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a>
|
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a>
|
||||
*/
|
||||
public interface UserInfo extends OAuth2User {
|
||||
public interface OidcUser extends OAuth2User, IdTokenClaimAccessor {
|
||||
|
||||
String getSubject();
|
||||
Map<String, Object> getClaims();
|
||||
|
||||
String getGivenName();
|
||||
|
||||
String getFamilyName();
|
||||
|
||||
String getMiddleName();
|
||||
|
||||
String getNickName();
|
||||
|
||||
String getPreferredUsername();
|
||||
|
||||
String getProfile();
|
||||
|
||||
String getPicture();
|
||||
|
||||
String getWebsite();
|
||||
|
||||
String getEmail();
|
||||
|
||||
Boolean getEmailVerified();
|
||||
|
||||
String getGender();
|
||||
|
||||
String getBirthdate();
|
||||
|
||||
String getZoneInfo();
|
||||
|
||||
String getLocale();
|
||||
|
||||
String getPhoneNumber();
|
||||
|
||||
Boolean getPhoneNumberVerified();
|
||||
|
||||
Address getAddress();
|
||||
|
||||
Instant getUpdatedAt();
|
||||
|
||||
|
||||
interface Address {
|
||||
|
||||
String getFormatted();
|
||||
|
||||
String getStreetAddress();
|
||||
|
||||
String getLocality();
|
||||
|
||||
String getRegion();
|
||||
|
||||
String getPostalCode();
|
||||
|
||||
String getCountry();
|
||||
}
|
||||
}
|
|
@ -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.oidc.core.user;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
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}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see OidcUser
|
||||
*/
|
||||
public class OidcUserAuthority extends OAuth2UserAuthority {
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
private final IdToken idToken;
|
||||
private final UserInfo userInfo;
|
||||
|
||||
public OidcUserAuthority(IdToken idToken) {
|
||||
this(idToken, null);
|
||||
}
|
||||
|
||||
public OidcUserAuthority(IdToken idToken, UserInfo userInfo) {
|
||||
this("ROLE_USER", idToken, userInfo);
|
||||
}
|
||||
|
||||
public OidcUserAuthority(String authority, IdToken idToken, UserInfo userInfo) {
|
||||
super(authority, idToken.getClaims());
|
||||
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() {
|
||||
return this.idToken;
|
||||
}
|
||||
|
||||
public UserInfo getUserInfo() {
|
||||
return this.userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OidcUserAuthority that = (OidcUserAuthority) obj;
|
||||
|
||||
if (!this.getIdToken().equals(that.getIdToken())) {
|
||||
return false;
|
||||
}
|
||||
return this.getUserInfo() != null ? this.getUserInfo().equals(that.getUserInfo()) : that.getUserInfo() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + this.getIdToken().hashCode();
|
||||
result = 31 * result + (this.getUserInfo() != null ? this.getUserInfo().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -16,4 +16,4 @@
|
|||
/**
|
||||
* Provides a model for an <i>OpenID Connect Core 1.0</i> representation of a user <code>Principal</code>.
|
||||
*/
|
||||
package org.springframework.security.oauth2.oidc.user;
|
||||
package org.springframework.security.oauth2.oidc.core.user;
|
|
@ -1,154 +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.oidc.user;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.oidc.StandardClaimName;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.security.oauth2.oidc.StandardClaimName.*;
|
||||
|
||||
/**
|
||||
* The default implementation of a {@link UserInfo}.
|
||||
*
|
||||
* <p>
|
||||
* The <i>key</i> used for accessing the "name" of the
|
||||
* <code>Principal</code> (user) via {@link #getAttributes()}
|
||||
* is {@link StandardClaimName#NAME} or if not available
|
||||
* will default to {@link StandardClaimName#SUB}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see UserInfo
|
||||
* @see DefaultOAuth2User
|
||||
*/
|
||||
public class DefaultUserInfo extends DefaultOAuth2User implements UserInfo {
|
||||
|
||||
public DefaultUserInfo(Map<String, Object> attributes) {
|
||||
this(Collections.emptySet(), attributes);
|
||||
}
|
||||
|
||||
public DefaultUserInfo(Set<GrantedAuthority> authorities, Map<String, Object> attributes) {
|
||||
super(authorities, attributes, SUB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return this.getAttributeAsString(SUB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String name = this.getAttributeAsString(NAME);
|
||||
return (name != null ? name : super.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGivenName() {
|
||||
return this.getAttributeAsString(GIVEN_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return this.getAttributeAsString(FAMILY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMiddleName() {
|
||||
return this.getAttributeAsString(MIDDLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNickName() {
|
||||
return this.getAttributeAsString(NICKNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredUsername() {
|
||||
return this.getAttributeAsString(PREFERRED_USERNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProfile() {
|
||||
return this.getAttributeAsString(PROFILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPicture() {
|
||||
return this.getAttributeAsString(PICTURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return this.getAttributeAsString(WEBSITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return this.getAttributeAsString(EMAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getEmailVerified() {
|
||||
return this.getAttributeAsBoolean(EMAIL_VERIFIED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGender() {
|
||||
return this.getAttributeAsString(GENDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBirthdate() {
|
||||
return this.getAttributeAsString(BIRTHDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getZoneInfo() {
|
||||
return this.getAttributeAsString(ZONEINFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
return this.getAttributeAsString(LOCALE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPhoneNumber() {
|
||||
return this.getAttributeAsString(PHONE_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getPhoneNumberVerified() {
|
||||
return this.getAttributeAsBoolean(PHONE_NUMBER_VERIFIED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
// TODO Impl
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getUpdatedAt() {
|
||||
return this.getAttributeAsInstant(UPDATED_AT);
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeRequestRedirectFilter;
|
||||
|
@ -48,6 +49,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
|
|||
import org.springframework.security.oauth2.core.endpoint.ResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
@ -389,7 +391,11 @@ public class OAuth2LoginApplicationTests {
|
|||
attributes.put("last-name", "Grandja");
|
||||
attributes.put("email", "joeg@springsecurity.io");
|
||||
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(attributes, "email");
|
||||
GrantedAuthority authority = new OAuth2UserAuthority(attributes);
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorities.add(authority);
|
||||
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(authorities, attributes, "email");
|
||||
|
||||
OAuth2UserService mock = mock(OAuth2UserService.class);
|
||||
when(mock.loadUser(any())).thenReturn(user);
|
||||
|
|
|
@ -24,21 +24,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.user.converter.AbstractOAuth2UserConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.springframework.boot.autoconfigure.security.oauth2.client.ClientRegistrationAutoConfiguration.*;
|
||||
|
||||
|
@ -54,8 +48,7 @@ import static org.springframework.boot.autoconfigure.security.oauth2.client.Clie
|
|||
@AutoConfigureAfter(ClientRegistrationAutoConfiguration.class)
|
||||
public class OAuth2LoginAutoConfiguration {
|
||||
private static final String USER_INFO_URI_PROPERTY = "user-info-uri";
|
||||
private static final String USER_INFO_CONVERTER_PROPERTY = "user-info-converter";
|
||||
private static final String USER_INFO_NAME_ATTR_KEY_PROPERTY = "user-info-name-attribute-key";
|
||||
private static final String USER_NAME_ATTR_NAME_PROPERTY = "user-name-attribute-name";
|
||||
|
||||
@EnableWebSecurity
|
||||
protected static class OAuth2LoginSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
@ -75,11 +68,11 @@ public class OAuth2LoginAutoConfiguration {
|
|||
.and()
|
||||
.oauth2Login();
|
||||
|
||||
this.registerUserInfoTypeConverters(http.oauth2Login());
|
||||
this.registerUserNameAttributeNames(http.oauth2Login());
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
private void registerUserInfoTypeConverters(OAuth2LoginConfigurer<HttpSecurity> oauth2LoginConfigurer) throws Exception {
|
||||
private void registerUserNameAttributeNames(OAuth2LoginConfigurer<HttpSecurity> oauth2LoginConfigurer) throws Exception {
|
||||
Set<String> clientPropertyKeys = resolveClientPropertyKeys(this.environment);
|
||||
for (String clientPropertyKey : clientPropertyKeys) {
|
||||
String fullClientPropertyKey = CLIENT_PROPERTY_PREFIX + "." + clientPropertyKey;
|
||||
|
@ -87,22 +80,9 @@ public class OAuth2LoginAutoConfiguration {
|
|||
continue;
|
||||
}
|
||||
String userInfoUriValue = this.environment.getProperty(fullClientPropertyKey + "." + USER_INFO_URI_PROPERTY);
|
||||
String userInfoConverterTypeValue = this.environment.getProperty(fullClientPropertyKey + "." + USER_INFO_CONVERTER_PROPERTY);
|
||||
if (userInfoUriValue != null && userInfoConverterTypeValue != null) {
|
||||
Class<? extends Function> userInfoConverterType = ClassUtils.resolveClassName(
|
||||
userInfoConverterTypeValue, this.getClass().getClassLoader()).asSubclass(Function.class);
|
||||
Function<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = null;
|
||||
if (AbstractOAuth2UserConverter.class.isAssignableFrom(userInfoConverterType)) {
|
||||
Constructor<? extends Function> oauth2UserConverterConstructor = ClassUtils.getConstructorIfAvailable(userInfoConverterType, String.class);
|
||||
if (oauth2UserConverterConstructor != null) {
|
||||
String userInfoNameAttributeKey = this.environment.getProperty(fullClientPropertyKey + "." + USER_INFO_NAME_ATTR_KEY_PROPERTY);
|
||||
userInfoConverter = (Function<ClientHttpResponse, ? extends OAuth2User>)oauth2UserConverterConstructor.newInstance(userInfoNameAttributeKey);
|
||||
}
|
||||
}
|
||||
if (userInfoConverter == null) {
|
||||
userInfoConverter = (Function<ClientHttpResponse, ? extends OAuth2User>)userInfoConverterType.newInstance();
|
||||
}
|
||||
oauth2LoginConfigurer.userInfoEndpoint().userInfoTypeConverter(userInfoConverter, new URI(userInfoUriValue));
|
||||
String userNameAttributeNameValue = this.environment.getProperty(fullClientPropertyKey + "." + USER_NAME_ATTR_NAME_PROPERTY);
|
||||
if (userInfoUriValue != null && userNameAttributeNameValue != null) {
|
||||
oauth2LoginConfigurer.userInfoEndpoint().userNameAttributeName(userNameAttributeNameValue, URI.create(userInfoUriValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,17 +16,19 @@
|
|||
package sample.user;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class GitHubOAuth2User implements OAuth2User {
|
||||
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||
private String id;
|
||||
private String name;
|
||||
private String login;
|
||||
|
@ -37,7 +39,7 @@ public class GitHubOAuth2User implements OAuth2User {
|
|||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.emptyList();
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,7 +9,7 @@ security:
|
|||
authorization-uri: "https://accounts.google.com/o/oauth2/auth"
|
||||
token-uri: "https://accounts.google.com/o/oauth2/token"
|
||||
user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
|
||||
client-name: Google
|
||||
client-alias: google
|
||||
github:
|
||||
|
@ -20,7 +20,6 @@ security:
|
|||
authorization-uri: "https://github.com/login/oauth/authorize"
|
||||
token-uri: "https://github.com/login/oauth/access_token"
|
||||
user-info-uri: "https://api.github.com/user"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: GitHub
|
||||
client-alias: github
|
||||
facebook:
|
||||
|
@ -31,7 +30,6 @@ security:
|
|||
authorization-uri: "https://www.facebook.com/v2.8/dialog/oauth"
|
||||
token-uri: "https://graph.facebook.com/v2.8/oauth/access_token"
|
||||
user-info-uri: "https://graph.facebook.com/me"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: Facebook
|
||||
client-alias: facebook
|
||||
okta:
|
||||
|
@ -39,6 +37,5 @@ security:
|
|||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: openid, email, profile
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
client-name: Okta
|
||||
client-alias: okta
|
||||
|
|
|
@ -21,14 +21,15 @@ security:
|
|||
github:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
user-info-name-attribute-key: "name"
|
||||
user-name-attribute-name: "name"
|
||||
facebook:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
user-info-name-attribute-key: "name"
|
||||
user-name-attribute-name: "name"
|
||||
okta:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
||||
|
|
Loading…
Reference in New Issue