Rename OAuth2/OIDC ClientAuthenticationToken -> AuthorizedClient

Fixes gh-4695
This commit is contained in:
Joe Grandja 2017-10-25 12:20:31 -04:00
parent 9b670882b7
commit 5a584e5ccb
14 changed files with 117 additions and 125 deletions

View File

@ -15,26 +15,22 @@
*/ */
package org.springframework.security.oauth2.client.authentication; package org.springframework.security.oauth2.client.authentication;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.Set; import java.util.Set;
/** /**
* An implementation of an {@link AbstractAuthenticationToken} * A representation of an OAuth 2.0 <i>&quot;Authorized Client&quot;</i>.
* that represents an <i>OAuth 2.0 Client</i> {@link Authentication}.
*
* <p> * <p>
* A client is considered <i>&quot;authenticated&quot;</i>, * A client is considered <i>&quot;authorized&quot;</i>
* if it receives a successful response from the <i>Token Endpoint</i>. * when it receives a successful response from the <i>Token Endpoint</i>.
* This {@link Authentication} associates the client identified in {@link #getClientRegistration()} * <p>
* to the {@link #getAccessToken()} granted by the resource owner. * This class associates the {@link #getClientRegistration() Client}
* to the {@link #getAccessToken() Access Token}
* granted/authorized by the {@link #getPrincipalName() Resource Owner}.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
@ -42,34 +38,28 @@ import java.util.Set;
* @see AccessToken * @see AccessToken
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.1">Section 5.1 Access Token Response</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.1">Section 5.1 Access Token Response</a>
*/ */
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken { public class AuthorizedClient {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final ClientRegistration clientRegistration; private final ClientRegistration clientRegistration;
private final String principalName;
private final AccessToken accessToken; private final AccessToken accessToken;
public OAuth2ClientAuthenticationToken(ClientRegistration clientRegistration, AccessToken accessToken) { public AuthorizedClient(ClientRegistration clientRegistration, String principalName, AccessToken accessToken) {
super(Collections.emptyList());
Assert.notNull(clientRegistration, "clientRegistration cannot be null"); Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.hasText(principalName, "principalName cannot be empty");
Assert.notNull(accessToken, "accessToken cannot be null"); Assert.notNull(accessToken, "accessToken cannot be null");
this.clientRegistration = clientRegistration; this.clientRegistration = clientRegistration;
this.principalName = principalName;
this.accessToken = accessToken; this.accessToken = accessToken;
this.setAuthenticated(true); // The Client is authenticated by the Authorization Server
}
@Override
public Object getPrincipal() {
return this.getClientRegistration().getClientId();
}
@Override
public Object getCredentials() {
return ""; // No need to expose this.getClientRegistration().getClientSecret()
} }
public ClientRegistration getClientRegistration() { public ClientRegistration getClientRegistration() {
return this.clientRegistration; return this.clientRegistration;
} }
public String getPrincipalName() {
return this.principalName;
}
public AccessToken getAccessToken() { public AccessToken getAccessToken() {
return this.accessToken; return this.accessToken;
} }

View File

@ -51,7 +51,7 @@ import java.util.Collection;
* @see AuthorizationCodeAuthenticationToken * @see AuthorizationCodeAuthenticationToken
* @see SecurityTokenRepository * @see SecurityTokenRepository
* @see OAuth2AuthenticationToken * @see OAuth2AuthenticationToken
* @see OAuth2ClientAuthenticationToken * @see AuthorizedClient
* @see OAuth2UserService * @see OAuth2UserService
* @see OAuth2User * @see OAuth2User
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
@ -118,22 +118,25 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(), tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(),
tokenResponse.getExpiresAt(), tokenResponse.getScopes()); tokenResponse.getExpiresAt(), tokenResponse.getScopes());
OAuth2ClientAuthenticationToken clientAuthentication = AuthorizedClient authorizedClient = new AuthorizedClient(
new OAuth2ClientAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), accessToken); authorizationCodeAuthentication.getClientRegistration(), "unknown", accessToken);
clientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
this.accessTokenRepository.saveSecurityToken( this.accessTokenRepository.saveSecurityToken(
clientAuthentication.getAccessToken(), authorizedClient.getAccessToken(),
clientAuthentication.getClientRegistration()); authorizedClient.getClientRegistration());
OAuth2User oauth2User = this.userService.loadUser(clientAuthentication); OAuth2User oauth2User = this.userService.loadUser(authorizedClient);
// Update AuthorizedClient now that we know the 'principalName'
authorizedClient = new AuthorizedClient(
authorizationCodeAuthentication.getClientRegistration(), oauth2User.getName(), accessToken);
Collection<? extends GrantedAuthority> mappedAuthorities = Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities()); this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken( OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(
oauth2User, mappedAuthorities, clientAuthentication); oauth2User, mappedAuthorities, authorizedClient);
authenticationResult.setDetails(clientAuthentication.getDetails()); authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult; return authenticationResult;
} }

View File

@ -15,10 +15,8 @@
*/ */
package org.springframework.security.oauth2.client.authentication.userinfo; package org.springframework.security.oauth2.client.authentication.userinfo;
import org.springframework.beans.BeanWrapper; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -53,14 +51,14 @@ public class CustomUserTypesOAuth2UserService implements OAuth2UserService {
} }
@Override @Override
public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { public OAuth2User loadUser(AuthorizedClient authorizedClient) throws OAuth2AuthenticationException {
URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri()); URI userInfoUri = URI.create(authorizedClient.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
Class<? extends OAuth2User> customUserType; Class<? extends OAuth2User> customUserType;
if ((customUserType = this.customUserTypes.get(userInfoUri)) == null) { if ((customUserType = this.customUserTypes.get(userInfoUri)) == null) {
return null; return null;
} }
return this.userInfoRetriever.retrieve(clientAuthentication, customUserType); return this.userInfoRetriever.retrieve(authorizedClient, customUserType);
} }
public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) { public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {

View File

@ -17,7 +17,7 @@ package org.springframework.security.oauth2.client.authentication.userinfo;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
@ -52,15 +52,15 @@ public class DefaultOAuth2UserService implements OAuth2UserService {
private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever(); private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
@Override @Override
public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { public OAuth2User loadUser(AuthorizedClient authorizedClient) throws OAuth2AuthenticationException {
String userNameAttributeName = clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); String userNameAttributeName = authorizedClient.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
if (!StringUtils.hasText(userNameAttributeName)) { if (!StringUtils.hasText(userNameAttributeName)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " + "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " +
clientAuthentication.getClientRegistration().getRegistrationId()); authorizedClient.getClientRegistration().getRegistrationId());
} }
Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(clientAuthentication, Map.class); Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(authorizedClient, Map.class);
GrantedAuthority authority = new OAuth2UserAuthority(userAttributes); GrantedAuthority authority = new OAuth2UserAuthority(userAttributes);
Set<GrantedAuthority> authorities = new HashSet<>(); Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(authority); authorities.add(authority);

View File

@ -16,7 +16,7 @@
package org.springframework.security.oauth2.client.authentication.userinfo; package org.springframework.security.oauth2.client.authentication.userinfo;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -30,7 +30,7 @@ import java.util.Objects;
* to it's internal <code>List</code> of {@link OAuth2UserService}'s. * to it's internal <code>List</code> of {@link OAuth2UserService}'s.
* <p> * <p>
* Each {@link OAuth2UserService} is given a chance to * Each {@link OAuth2UserService} is given a chance to
* {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken) load} an {@link OAuth2User} * {@link OAuth2UserService#loadUser(AuthorizedClient) load} an {@link OAuth2User}
* with the first <code>non-null</code> {@link OAuth2User} being returned. * with the first <code>non-null</code> {@link OAuth2User} being returned.
* *
* @author Joe Grandja * @author Joe Grandja
@ -47,9 +47,9 @@ public class DelegatingOAuth2UserService implements OAuth2UserService {
} }
@Override @Override
public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { public OAuth2User loadUser(AuthorizedClient authorizedClient) throws OAuth2AuthenticationException {
OAuth2User oauth2User = this.userServices.stream() OAuth2User oauth2User = this.userServices.stream()
.map(userService -> userService.loadUser(clientAuthentication)) .map(userService -> userService.loadUser(authorizedClient))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(null); .orElse(null);

View File

@ -28,7 +28,7 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -37,7 +37,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Map;
/** /**
* An implementation of a {@link UserInfoRetriever} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally. * An implementation of a {@link UserInfoRetriever} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
@ -52,9 +51,9 @@ public class NimbusUserInfoRetriever implements UserInfoRetriever {
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
@Override @Override
public <T> T retrieve(OAuth2ClientAuthenticationToken clientAuthentication, Class<T> returnType) throws OAuth2AuthenticationException { public <T> T retrieve(AuthorizedClient authorizedClient, Class<T> returnType) throws OAuth2AuthenticationException {
URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri()); URI userInfoUri = URI.create(authorizedClient.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
BearerAccessToken accessToken = new BearerAccessToken(clientAuthentication.getAccessToken().getTokenValue()); BearerAccessToken accessToken = new BearerAccessToken(authorizedClient.getAccessToken().getTokenValue());
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken); UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest(); HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();

View File

@ -19,7 +19,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -28,28 +28,28 @@ import java.util.Collection;
/** /**
* An implementation of an {@link AbstractAuthenticationToken} * An implementation of an {@link AbstractAuthenticationToken}
* that represents an <i>OAuth 2.0</i> {@link Authentication}. * that represents an <i>OAuth 2.0</i> {@link Authentication}.
*
* <p> * <p>
* This {@link Authentication} associates an {@link OAuth2User} principal to an * This {@link Authentication} associates an {@link OAuth2User} principal
* {@link OAuth2ClientAuthenticationToken} which represents the <i>&quot;Authorized Client&quot;</i>. * to an {@link AuthorizedClient}.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see OAuth2User * @see OAuth2User
* @see OAuth2ClientAuthenticationToken * @see AuthorizedClient
*/ */
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken { public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final OAuth2User principal; private final OAuth2User principal;
private final OAuth2ClientAuthenticationToken clientAuthentication; private final AuthorizedClient authorizedClient;
public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities, public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities,
OAuth2ClientAuthenticationToken clientAuthentication) { AuthorizedClient authorizedClient) {
super(authorities); super(authorities);
Assert.notNull(clientAuthentication, "clientAuthentication cannot be null"); Assert.notNull(principal, "principal cannot be null");
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
this.principal = principal; this.principal = principal;
this.clientAuthentication = clientAuthentication; this.authorizedClient = authorizedClient;
this.setAuthenticated(principal != null); this.setAuthenticated(true);
} }
@Override @Override
@ -63,7 +63,7 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
return ""; return "";
} }
public OAuth2ClientAuthenticationToken getClientAuthentication() { public AuthorizedClient getAuthorizedClient() {
return this.clientAuthentication; return this.authorizedClient;
} }
} }

View File

@ -16,24 +16,24 @@
package org.springframework.security.oauth2.client.authentication.userinfo; package org.springframework.security.oauth2.client.authentication.userinfo;
import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
/** /**
* Implementations of this interface are responsible for obtaining the user attributes * Implementations of this interface are responsible for obtaining the user attributes
* of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i> * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
* using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()} * using the provided {@link AuthorizedClient#getAccessToken()}
* and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}. * and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see OAuth2ClientAuthenticationToken * @see AuthorizedClient
* @see AuthenticatedPrincipal * @see AuthenticatedPrincipal
* @see OAuth2User * @see OAuth2User
*/ */
public interface OAuth2UserService { public interface OAuth2UserService {
OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException; OAuth2User loadUser(AuthorizedClient authorizedClient) throws OAuth2AuthenticationException;
} }

View File

@ -15,23 +15,22 @@
*/ */
package org.springframework.security.oauth2.client.authentication.userinfo; package org.springframework.security.oauth2.client.authentication.userinfo;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
/** /**
* A strategy for retrieving the user attributes * A strategy for retrieving the user attributes
* of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i> * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
* using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()}. * using the provided {@link AuthorizedClient#getAccessToken()}.
* *
* @author Joe Grandja * @author Joe Grandja
* @author Rob Winch * @author Rob Winch
* @since 5.0 * @since 5.0
* @see OAuth2ClientAuthenticationToken * @see AuthorizedClient
* @see OAuth2UserService * @see OAuth2UserService
*/ */
public interface UserInfoRetriever { public interface UserInfoRetriever {
<T> T retrieve(OAuth2ClientAuthenticationToken clientAuthentication, Class<T> responseType) throws OAuth2AuthenticationException; <T> T retrieve(AuthorizedClient clientAuthentication, Class<T> responseType) throws OAuth2AuthenticationException;
} }

View File

@ -62,7 +62,7 @@ import java.util.Collection;
* @since 5.0 * @since 5.0
* @see AuthorizationCodeAuthenticationToken * @see AuthorizationCodeAuthenticationToken
* @see SecurityTokenRepository * @see SecurityTokenRepository
* @see OidcClientAuthenticationToken * @see OidcAuthorizedClient
* @see OidcUserService * @see OidcUserService
* @see OidcUser * @see OidcUser
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 Authorization Code Grant Flow</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 Authorization Code Grant Flow</a>
@ -142,28 +142,32 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration); JwtDecoder jwtDecoder = this.jwtDecoderRegistry.getJwtDecoder(clientRegistration);
if (jwtDecoder == null) { if (jwtDecoder == null) {
throw new IllegalArgumentException("Failed to find a registered JwtDecoder for Client Registration: '" + clientRegistration.getRegistrationId() + throw new IllegalArgumentException("Failed to find a registered JwtDecoder for Client Registration: '" +
"'. Check to ensure you have configured the JwkSet URI."); clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.");
} }
Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN)); Jwt jwt = jwtDecoder.decode((String)tokenResponse.getAdditionalParameters().get(OidcParameter.ID_TOKEN));
IdToken idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()); IdToken idToken = new IdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
OidcClientAuthenticationToken clientAuthentication = OidcAuthorizedClient authorizedClient = new OidcAuthorizedClient(
new OidcClientAuthenticationToken(clientRegistration, accessToken, idToken); clientRegistration, idToken.getSubject(), accessToken, idToken);
clientAuthentication.setDetails(authorizationCodeAuthentication.getDetails());
this.accessTokenRepository.saveSecurityToken( this.accessTokenRepository.saveSecurityToken(
clientAuthentication.getAccessToken(), authorizedClient.getAccessToken(),
clientAuthentication.getClientRegistration()); authorizedClient.getClientRegistration());
OAuth2User oauth2User = this.userService.loadUser(clientAuthentication); OAuth2User oauth2User = this.userService.loadUser(authorizedClient);
// Update AuthorizedClient as the 'principalName' may have changed
// (the default IdToken.subject) from the result of userService.loadUser()
authorizedClient = new OidcAuthorizedClient(
clientRegistration, oauth2User.getName(), accessToken, idToken);
Collection<? extends GrantedAuthority> mappedAuthorities = Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities()); this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken( OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(
oauth2User, mappedAuthorities, clientAuthentication); oauth2User, mappedAuthorities, authorizedClient);
authenticationResult.setDetails(clientAuthentication.getDetails()); authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult; return authenticationResult;
} }

View File

@ -15,37 +15,36 @@
*/ */
package org.springframework.security.oauth2.oidc.client.authentication; package org.springframework.security.oauth2.oidc.client.authentication;
import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.IdToken;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* An {@link OAuth2ClientAuthenticationToken} that represents an * A representation of an OpenID Connect 1.0 <i>&quot;Authorized Client&quot;</i>.
* <i>OpenID Connect 1.0 Client</i> {@link Authentication}.
*
* <p> * <p>
* A client is considered <i>&quot;authenticated&quot;</i>, * A client is considered <i>&quot;authorized&quot;</i>
* if it receives a successful response from the <i>Token Endpoint</i>. * when it receives a successful response from the <i>Token Endpoint</i>.
* This {@link Authentication} associates the client identified in {@link #getClientRegistration()} * <p>
* to the {@link #getAccessToken()} granted by the resource owner along with the {@link #getIdToken()} * This class associates the {@link #getClientRegistration() Client}
* containing Claims about the authentication of the End-User. * to the {@link #getAccessToken() Access Token}
* granted/authorized by the {@link #getPrincipalName() Resource Owner}, along with
* the {@link #getIdToken() ID Token} which contains Claims about the authentication of the End-User.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see IdToken * @see IdToken
* @see OAuth2ClientAuthenticationToken * @see AuthorizedClient
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">3.1.3.3 Successful Token Response</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">3.1.3.3 Successful Token Response</a>
*/ */
public class OidcClientAuthenticationToken extends OAuth2ClientAuthenticationToken { public class OidcAuthorizedClient extends AuthorizedClient {
private final IdToken idToken; private final IdToken idToken;
public OidcClientAuthenticationToken(ClientRegistration clientRegistration, public OidcAuthorizedClient(ClientRegistration clientRegistration, String principalName,
AccessToken accessToken, IdToken idToken) { AccessToken accessToken, IdToken idToken) {
super(clientRegistration, accessToken); super(clientRegistration, principalName, accessToken);
Assert.notNull(idToken, "idToken cannot be null"); Assert.notNull(idToken, "idToken cannot be null");
this.idToken = idToken; this.idToken = idToken;
} }

View File

@ -16,15 +16,15 @@
package org.springframework.security.oauth2.oidc.client.authentication.userinfo; package org.springframework.security.oauth2.oidc.client.authentication.userinfo;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.userinfo.NimbusUserInfoRetriever; import org.springframework.security.oauth2.client.authentication.userinfo.NimbusUserInfoRetriever;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.authentication.userinfo.UserInfoRetriever; import org.springframework.security.oauth2.client.authentication.userinfo.UserInfoRetriever;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizedClient;
import org.springframework.security.oauth2.oidc.core.OidcScope; import org.springframework.security.oauth2.oidc.core.OidcScope;
import org.springframework.security.oauth2.oidc.core.UserInfo; 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.DefaultOidcUser;
@ -47,7 +47,7 @@ import java.util.Set;
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see OAuth2UserService * @see OAuth2UserService
* @see OidcClientAuthenticationToken * @see OidcAuthorizedClient
* @see DefaultOidcUser * @see DefaultOidcUser
* @see UserInfo * @see UserInfo
* @see UserInfoRetriever * @see UserInfoRetriever
@ -59,12 +59,12 @@ public class OidcUserService implements OAuth2UserService {
Arrays.asList(OidcScope.PROFILE, OidcScope.EMAIL, OidcScope.ADDRESS, OidcScope.PHONE)); Arrays.asList(OidcScope.PROFILE, OidcScope.EMAIL, OidcScope.ADDRESS, OidcScope.PHONE));
@Override @Override
public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { public OAuth2User loadUser(AuthorizedClient authorizedClient) throws OAuth2AuthenticationException {
OidcClientAuthenticationToken oidcClientAuthentication = (OidcClientAuthenticationToken)clientAuthentication; OidcAuthorizedClient oidcAuthorizedClient = (OidcAuthorizedClient)authorizedClient;
UserInfo userInfo = null; UserInfo userInfo = null;
if (this.shouldRetrieveUserInfo(oidcClientAuthentication)) { if (this.shouldRetrieveUserInfo(oidcAuthorizedClient)) {
Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(oidcClientAuthentication, Map.class); Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(oidcAuthorizedClient, Map.class);
userInfo = new UserInfo(userAttributes); userInfo = new UserInfo(userAttributes);
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
@ -74,17 +74,17 @@ public class OidcUserService implements OAuth2UserService {
// The sub Claim in the UserInfo Response MUST be verified to exactly match // The sub Claim in the UserInfo Response MUST be verified to exactly match
// the sub Claim in the ID Token; if they do not match, // the sub Claim in the ID Token; if they do not match,
// the UserInfo Response values MUST NOT be used. // the UserInfo Response values MUST NOT be used.
if (!userInfo.getSubject().equals(oidcClientAuthentication.getIdToken().getSubject())) { if (!userInfo.getSubject().equals(oidcAuthorizedClient.getIdToken().getSubject())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE); OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
} }
} }
GrantedAuthority authority = new OidcUserAuthority(oidcClientAuthentication.getIdToken(), userInfo); GrantedAuthority authority = new OidcUserAuthority(oidcAuthorizedClient.getIdToken(), userInfo);
Set<GrantedAuthority> authorities = new HashSet<>(); Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(authority); authorities.add(authority);
return new DefaultOidcUser(authorities, oidcClientAuthentication.getIdToken(), userInfo); return new DefaultOidcUser(authorities, oidcAuthorizedClient.getIdToken(), userInfo);
} }
public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) { public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
@ -92,9 +92,9 @@ public class OidcUserService implements OAuth2UserService {
this.userInfoRetriever = userInfoRetriever; this.userInfoRetriever = userInfoRetriever;
} }
private boolean shouldRetrieveUserInfo(OidcClientAuthenticationToken oidcClientAuthentication) { private boolean shouldRetrieveUserInfo(OidcAuthorizedClient oidcAuthorizedClient) {
// Auto-disabled if UserInfo Endpoint URI is not provided // Auto-disabled if UserInfo Endpoint URI is not provided
if (StringUtils.isEmpty(oidcClientAuthentication.getClientRegistration().getProviderDetails() if (StringUtils.isEmpty(oidcAuthorizedClient.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUri())) { .getUserInfoEndpoint().getUri())) {
return false; return false;
@ -107,10 +107,10 @@ public class OidcUserService implements OAuth2UserService {
// the resulting Claims are returned in the ID Token. // the resulting Claims are returned in the ID Token.
// The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued. // The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals( if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
oidcClientAuthentication.getClientRegistration().getAuthorizationGrantType())) { oidcAuthorizedClient.getClientRegistration().getAuthorizationGrantType())) {
// Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s) // Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s)
return oidcClientAuthentication.getAuthorizedScopes().stream().anyMatch(userInfoScopes::contains); return oidcAuthorizedClient.getAuthorizedScopes().stream().anyMatch(userInfoScopes::contains);
} }
return false; return false;

View File

@ -28,7 +28,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@ -98,13 +98,13 @@ public class OAuth2LoginAuthenticationFilterTests {
@Test @Test
public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSuccessHandlerIsCalled() throws Exception { public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSuccessHandlerIsCalled() throws Exception {
ClientRegistration clientRegistration = TestUtil.githubClientRegistration(); ClientRegistration clientRegistration = TestUtil.githubClientRegistration();
OAuth2ClientAuthenticationToken clientAuthentication = new OAuth2ClientAuthenticationToken( AuthorizedClient authorizedClient = new AuthorizedClient(
clientRegistration, mock(AccessToken.class)); clientRegistration, "principal", mock(AccessToken.class));
OAuth2AuthenticationToken userAuthentication = new OAuth2AuthenticationToken( OAuth2AuthenticationToken userAuthentication = new OAuth2AuthenticationToken(
mock(OAuth2User.class), AuthorityUtils.createAuthorityList("ROLE_USER"), clientAuthentication); mock(OAuth2User.class), AuthorityUtils.createAuthorityList("ROLE_USER"), authorizedClient);
SecurityContextHolder.getContext().setAuthentication(userAuthentication); SecurityContextHolder.getContext().setAuthentication(userAuthentication);
AuthenticationManager authenticationManager = mock(AuthenticationManager.class); AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
Mockito.when(authenticationManager.authenticate(Matchers.any(Authentication.class))).thenReturn(clientAuthentication); Mockito.when(authenticationManager.authenticate(Matchers.any(Authentication.class))).thenReturn(userAuthentication);
OAuth2LoginAuthenticationFilter filter = Mockito.spy(setupFilter(authenticationManager, clientRegistration)); OAuth2LoginAuthenticationFilter filter = Mockito.spy(setupFilter(authenticationManager, clientRegistration));
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class); AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);
@ -128,7 +128,7 @@ public class OAuth2LoginAuthenticationFilterTests {
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class); ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class);
Mockito.verify(successHandler).onAuthenticationSuccess(Matchers.any(HttpServletRequest.class), Matchers.any(HttpServletResponse.class), Mockito.verify(successHandler).onAuthenticationSuccess(Matchers.any(HttpServletRequest.class), Matchers.any(HttpServletResponse.class),
authenticationArgCaptor.capture()); authenticationArgCaptor.capture());
Assertions.assertThat(authenticationArgCaptor.getValue()).isEqualTo(clientAuthentication); Assertions.assertThat(authenticationArgCaptor.getValue()).isEqualTo(userAuthentication);
} }
@Test @Test

View File

@ -40,14 +40,14 @@ public class MainController {
@RequestMapping("/") @RequestMapping("/")
public String index(Model model, @AuthenticationPrincipal OAuth2User user, OAuth2AuthenticationToken authentication) { public String index(Model model, @AuthenticationPrincipal OAuth2User user, OAuth2AuthenticationToken authentication) {
model.addAttribute("userName", user.getName()); model.addAttribute("userName", user.getName());
model.addAttribute("clientName", authentication.getClientAuthentication().getClientRegistration().getClientName()); model.addAttribute("clientName", authentication.getAuthorizedClient().getClientRegistration().getClientName());
return "index"; return "index";
} }
@RequestMapping("/userinfo") @RequestMapping("/userinfo")
public String userinfo(Model model, OAuth2AuthenticationToken authentication) { public String userinfo(Model model, OAuth2AuthenticationToken authentication) {
Map userAttributes = Collections.emptyMap(); Map userAttributes = Collections.emptyMap();
String userInfoEndpointUri = authentication.getClientAuthentication().getClientRegistration() String userInfoEndpointUri = authentication.getAuthorizedClient().getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri(); .getProviderDetails().getUserInfoEndpoint().getUri();
if (!StringUtils.isEmpty(userInfoEndpointUri)) { // userInfoEndpointUri is optional for OIDC Clients if (!StringUtils.isEmpty(userInfoEndpointUri)) { // userInfoEndpointUri is optional for OIDC Clients
userAttributes = WebClient.builder() userAttributes = WebClient.builder()
@ -67,7 +67,7 @@ public class MainController {
return ExchangeFilterFunction.ofRequestProcessor( return ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> { clientRequest -> {
ClientRequest authorizedRequest = ClientRequest.from(clientRequest) ClientRequest authorizedRequest = ClientRequest.from(clientRequest)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authentication.getClientAuthentication().getAccessToken().getTokenValue()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + authentication.getAuthorizedClient().getAccessToken().getTokenValue())
.build(); .build();
return Mono.just(authorizedRequest); return Mono.just(authorizedRequest);
}); });