Refactor OAuth2 AuthenticationProvider's

Fixes gh-4689
This commit is contained in:
Joe Grandja 2017-10-24 15:11:35 -04:00
parent 0fb32a052e
commit 049080290e
10 changed files with 112 additions and 337 deletions

View File

@ -29,7 +29,6 @@ import org.springframework.security.oauth2.client.authentication.jwt.NimbusJwtDe
import org.springframework.security.oauth2.client.authentication.userinfo.CustomUserTypesOAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.CustomUserTypesOAuth2UserService;
import org.springframework.security.oauth2.client.authentication.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.authentication.userinfo.DelegatingOAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.DelegatingOAuth2UserService;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService;
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;
@ -42,7 +41,6 @@ import org.springframework.security.oauth2.core.AccessToken;
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestUriBuilder; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestUriBuilder;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.oidc.client.authentication.OidcAuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.oidc.client.authentication.userinfo.OidcUserAuthenticationProvider;
import org.springframework.security.oauth2.oidc.client.authentication.userinfo.OidcUserService; import org.springframework.security.oauth2.oidc.client.authentication.userinfo.OidcUserService;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -232,58 +230,53 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger(); authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();
} }
OAuth2UserService oauth2UserService = this.userInfoEndpointConfig.userService;
if (oauth2UserService == null) {
if (!this.userInfoEndpointConfig.customUserTypes.isEmpty()) {
List<OAuth2UserService> userServices = new ArrayList<>();
userServices.add(new CustomUserTypesOAuth2UserService(this.userInfoEndpointConfig.customUserTypes));
userServices.add(new DefaultOAuth2UserService());
oauth2UserService = new DelegatingOAuth2UserService(userServices);
} else {
oauth2UserService = new DefaultOAuth2UserService();
}
}
JwtDecoderRegistry jwtDecoderRegistry = this.tokenEndpointConfig.jwtDecoderRegistry; JwtDecoderRegistry jwtDecoderRegistry = this.tokenEndpointConfig.jwtDecoderRegistry;
if (jwtDecoderRegistry == null) { if (jwtDecoderRegistry == null) {
jwtDecoderRegistry = new NimbusJwtDecoderRegistry(); jwtDecoderRegistry = new NimbusJwtDecoderRegistry();
} }
AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider = AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider =
new AuthorizationCodeAuthenticationProvider(authorizationCodeTokenExchanger); new AuthorizationCodeAuthenticationProvider(authorizationCodeTokenExchanger, oauth2UserService);
if (this.tokenEndpointConfig.accessTokenRepository != null) { if (this.tokenEndpointConfig.accessTokenRepository != null) {
oauth2AuthorizationCodeAuthenticationProvider.setAccessTokenRepository( oauth2AuthorizationCodeAuthenticationProvider.setAccessTokenRepository(
this.tokenEndpointConfig.accessTokenRepository); this.tokenEndpointConfig.accessTokenRepository);
} }
if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) {
oauth2AuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(
this.userInfoEndpointConfig.userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oauth2AuthorizationCodeAuthenticationProvider)); http.authenticationProvider(this.postProcess(oauth2AuthorizationCodeAuthenticationProvider));
OAuth2UserService oidcUserService = this.userInfoEndpointConfig.userService;
if (oidcUserService == null) {
oidcUserService = new OidcUserService();
}
OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider = OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider =
new OidcAuthorizationCodeAuthenticationProvider(authorizationCodeTokenExchanger, jwtDecoderRegistry); new OidcAuthorizationCodeAuthenticationProvider(
authorizationCodeTokenExchanger, oidcUserService, jwtDecoderRegistry);
if (this.tokenEndpointConfig.accessTokenRepository != null) { if (this.tokenEndpointConfig.accessTokenRepository != null) {
oidcAuthorizationCodeAuthenticationProvider.setAccessTokenRepository( oidcAuthorizationCodeAuthenticationProvider.setAccessTokenRepository(
this.tokenEndpointConfig.accessTokenRepository); this.tokenEndpointConfig.accessTokenRepository);
} }
if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) {
oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(
this.userInfoEndpointConfig.userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oidcAuthorizationCodeAuthenticationProvider)); http.authenticationProvider(this.postProcess(oidcAuthorizationCodeAuthenticationProvider));
OAuth2UserService userService = this.userInfoEndpointConfig.userService;
if (userService == null) {
if (!this.userInfoEndpointConfig.customUserTypes.isEmpty()) {
List<OAuth2UserService> userServices = new ArrayList<>();
userServices.add(new CustomUserTypesOAuth2UserService(this.userInfoEndpointConfig.customUserTypes));
userServices.add(new DefaultOAuth2UserService());
userService = new DelegatingOAuth2UserService(userServices);
} else {
userService = new DefaultOAuth2UserService();
}
}
OAuth2UserAuthenticationProvider oauth2UserAuthenticationProvider =
new OAuth2UserAuthenticationProvider(userService);
if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) {
oauth2UserAuthenticationProvider.setAuthoritiesMapper(this.userInfoEndpointConfig.userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oauth2UserAuthenticationProvider));
userService = this.userInfoEndpointConfig.userService;
if (userService == null) {
userService = new OidcUserService();
}
OidcUserAuthenticationProvider oidcUserAuthenticationProvider =
new OidcUserAuthenticationProvider(userService);
if (this.userInfoEndpointConfig.userAuthoritiesMapper != null) {
oidcUserAuthenticationProvider.setAuthoritiesMapper(this.userInfoEndpointConfig.userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oidcUserAuthenticationProvider));
this.initDefaultLoginFilter(http); this.initDefaultLoginFilter(http);
} }

View File

@ -18,6 +18,10 @@ package org.springframework.security.oauth2.client.authentication;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository;
import org.springframework.security.oauth2.client.token.SecurityTokenRepository; import org.springframework.security.oauth2.client.token.SecurityTokenRepository;
import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.AccessToken;
@ -25,8 +29,11 @@ import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.TokenResponse; import org.springframework.security.oauth2.core.endpoint.TokenResponse;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.Collection;
/** /**
* An implementation of an {@link AuthenticationProvider} * An implementation of an {@link AuthenticationProvider}
* for the <i>OAuth 2.0 Authorization Code Grant Flow</i>. * for the <i>OAuth 2.0 Authorization Code Grant Flow</i>.
@ -34,12 +41,19 @@ import org.springframework.util.Assert;
* This {@link AuthenticationProvider} is responsible for authenticating * This {@link AuthenticationProvider} is responsible for authenticating
* an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i> * an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i>
* and if valid, exchanging it for an <i>access token</i> credential. * and if valid, exchanging it for an <i>access token</i> credential.
* <p>
* It will also obtain the user attributes of the <i>End-User</i> (resource owner)
* from the <i>UserInfo Endpoint</i> using an {@link OAuth2UserService}
* which will create a <code>Principal</code> in the form of an {@link OAuth2User}.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see AuthorizationCodeAuthenticationToken * @see AuthorizationCodeAuthenticationToken
* @see OAuth2ClientAuthenticationToken
* @see SecurityTokenRepository * @see SecurityTokenRepository
* @see OAuth2AuthenticationToken
* @see OAuth2ClientAuthenticationToken
* @see OAuth2UserService
* @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>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
@ -48,13 +62,18 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger; private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final OAuth2UserService userService;
private SecurityTokenRepository<AccessToken> accessTokenRepository = new InMemoryAccessTokenRepository(); private SecurityTokenRepository<AccessToken> accessTokenRepository = new InMemoryAccessTokenRepository();
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public AuthorizationCodeAuthenticationProvider( public AuthorizationCodeAuthenticationProvider(
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger) { AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
OAuth2UserService userService) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
Assert.notNull(userService, "userService cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
this.userService = userService;
} }
@Override @Override
@ -107,7 +126,16 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
clientAuthentication.getAccessToken(), clientAuthentication.getAccessToken(),
clientAuthentication.getClientRegistration()); clientAuthentication.getClientRegistration());
return clientAuthentication; OAuth2User oauth2User = this.userService.loadUser(clientAuthentication);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(
oauth2User, mappedAuthorities, clientAuthentication);
authenticationResult.setDetails(clientAuthentication.getDetails());
return authenticationResult;
} }
public final void setAccessTokenRepository(SecurityTokenRepository<AccessToken> accessTokenRepository) { public final void setAccessTokenRepository(SecurityTokenRepository<AccessToken> accessTokenRepository) {
@ -115,6 +143,11 @@ public class AuthorizationCodeAuthenticationProvider implements AuthenticationPr
this.accessTokenRepository = accessTokenRepository; this.accessTokenRepository = accessTokenRepository;
} }
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
this.authoritiesMapper = authoritiesMapper;
}
@Override @Override
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);

View File

@ -27,7 +27,7 @@ import java.util.Collection;
/** /**
* An implementation of an {@link AbstractAuthenticationToken} * An implementation of an {@link AbstractAuthenticationToken}
* that represents an <i>OAuth 2.0 User</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 to an
@ -38,13 +38,13 @@ import java.util.Collection;
* @see OAuth2User * @see OAuth2User
* @see OAuth2ClientAuthenticationToken * @see OAuth2ClientAuthenticationToken
*/ */
public class OAuth2UserAuthenticationToken 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 OAuth2ClientAuthenticationToken clientAuthentication;
public OAuth2UserAuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities, public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities,
OAuth2ClientAuthenticationToken clientAuthentication) { OAuth2ClientAuthenticationToken clientAuthentication) {
super(authorities); super(authorities);
Assert.notNull(clientAuthentication, "clientAuthentication cannot be null"); Assert.notNull(clientAuthentication, "clientAuthentication cannot be null");
this.principal = principal; this.principal = principal;

View File

@ -1,131 +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.authentication.userinfo;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
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.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* An implementation of an {@link AuthenticationProvider} that is responsible
* for obtaining the user attributes of the <i>End-User</i> (resource owner)
* from the <i>UserInfo Endpoint</i> and creating a <code>Principal</code>
* in the form of an {@link OAuth2User}.
*
* <p>
* The {@link OAuth2UserAuthenticationProvider} uses an {@link OAuth2UserService}
* for loading the {@link OAuth2User} and then associating it
* to the returned {@link OAuth2UserAuthenticationToken}.
*
* @author Joe Grandja
* @since 5.0
* @see OAuth2UserAuthenticationToken
* @see OAuth2ClientAuthenticationToken
* @see OAuth2UserService
* @see OAuth2User
*/
public class OAuth2UserAuthenticationProvider implements AuthenticationProvider {
private final OAuth2UserService userService;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public OAuth2UserAuthenticationProvider(OAuth2UserService userService) {
Assert.notNull(userService, "userService cannot be null");
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken)authentication;
// Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
if (clientAuthentication.getAuthorizedScopes().contains("openid")) {
// This is an OpenID Connect Authentication Request so return null
// and let OidcUserAuthenticationProvider handle it instead
return null;
}
if (this.userAuthenticated() && this.userAuthenticatedSameProviderAs(clientAuthentication)) {
// Create a new user authentication (using same principal)
// but with a different client authentication association
OAuth2UserAuthenticationToken currentUserAuthentication =
(OAuth2UserAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
return new OAuth2UserAuthenticationToken(
(OAuth2User)currentUserAuthentication.getPrincipal(),
currentUserAuthentication.getAuthorities(),
clientAuthentication);
}
OAuth2User oauth2User = this.userService.loadUser(clientAuthentication);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2UserAuthenticationToken authenticationResult = new OAuth2UserAuthenticationToken(
oauth2User, mappedAuthorities, clientAuthentication);
authenticationResult.setDetails(clientAuthentication.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
}
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
this.authoritiesMapper = authoritiesMapper;
}
private boolean userAuthenticated() {
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
return currentAuthentication != null &&
currentAuthentication instanceof OAuth2UserAuthenticationToken &&
currentAuthentication.isAuthenticated();
}
private boolean userAuthenticatedSameProviderAs(OAuth2ClientAuthenticationToken clientAuthentication) {
OAuth2UserAuthenticationToken currentUserAuthentication =
(OAuth2UserAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
String userProviderId = this.getProviderIdentifier(
currentUserAuthentication.getClientAuthentication().getClientRegistration());
String clientProviderId = this.getProviderIdentifier(
clientAuthentication.getClientRegistration());
return userProviderId.equals(clientProviderId);
}
private String getProviderIdentifier(ClientRegistration clientRegistration) {
StringBuilder builder = new StringBuilder();
builder.append("[").append(clientRegistration.getProviderDetails().getAuthorizationUri()).append("]");
builder.append("[").append(clientRegistration.getProviderDetails().getTokenUri()).append("]");
builder.append("[").append(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()).append("]");
return builder.toString();
}
}

View File

@ -21,7 +21,6 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
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.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;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
@ -128,10 +127,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
clientRegistration, new AuthorizationExchange(authorizationRequest, authorizationResponse)); clientRegistration, new AuthorizationExchange(authorizationRequest, authorizationResponse));
authorizationCodeAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request)); authorizationCodeAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request));
OAuth2ClientAuthenticationToken clientAuthentication = return this.getAuthenticationManager().authenticate(authorizationCodeAuthentication);
(OAuth2ClientAuthenticationToken)this.getAuthenticationManager().authenticate(authorizationCodeAuthentication);
return this.getAuthenticationManager().authenticate(clientAuthentication);
} }
public final void setClientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { public final void setClientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) {

View File

@ -18,10 +18,14 @@ package org.springframework.security.oauth2.oidc.client.authentication;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException;
import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry; import org.springframework.security.oauth2.client.authentication.jwt.JwtDecoderRegistry;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository;
import org.springframework.security.oauth2.client.token.SecurityTokenRepository; import org.springframework.security.oauth2.client.token.SecurityTokenRepository;
@ -30,26 +34,37 @@ import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.TokenResponse; import org.springframework.security.oauth2.core.endpoint.TokenResponse;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.oidc.client.authentication.userinfo.OidcUserService;
import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.IdToken;
import org.springframework.security.oauth2.oidc.core.OidcScope; import org.springframework.security.oauth2.oidc.core.OidcScope;
import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter; import org.springframework.security.oauth2.oidc.core.endpoint.OidcParameter;
import org.springframework.security.oauth2.oidc.core.user.OidcUser;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.Collection;
/** /**
* An implementation of an {@link AuthenticationProvider} * An implementation of an {@link AuthenticationProvider}
* for the <i>OpenID Connect Core 1.0 Authorization Code Grant Flow</i>. * for the <i>OpenID Connect Core 1.0 Authorization Code Grant Flow</i>.
* * <p>
* This {@link AuthenticationProvider} is responsible for authenticating * This {@link AuthenticationProvider} is responsible for authenticating
* an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i> * an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i>
* and if valid, exchanging it for an <i>access token</i> credential. * and if valid, exchanging it for an <i>access token</i> credential.
* <p>
* It will also obtain the user attributes of the <i>End-User</i> (resource owner)
* from the <i>UserInfo Endpoint</i> using an {@link OAuth2UserService}
* which will create a <code>Principal</code> in the form of an {@link OidcUser}.
* *
* @author Joe Grandja * @author Joe Grandja
* @since 5.0 * @since 5.0
* @see AuthorizationCodeAuthenticationToken * @see AuthorizationCodeAuthenticationToken
* @see OidcClientAuthenticationToken
* @see SecurityTokenRepository * @see SecurityTokenRepository
* @see OidcClientAuthenticationToken
* @see OidcUserService
* @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>
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1 Token Request</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1 Token Request</a>
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 Token Response</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 Token Response</a>
@ -58,16 +73,21 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger; private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final OAuth2UserService userService;
private final JwtDecoderRegistry jwtDecoderRegistry; private final JwtDecoderRegistry jwtDecoderRegistry;
private SecurityTokenRepository<AccessToken> accessTokenRepository = new InMemoryAccessTokenRepository(); private SecurityTokenRepository<AccessToken> accessTokenRepository = new InMemoryAccessTokenRepository();
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public OidcAuthorizationCodeAuthenticationProvider( public OidcAuthorizationCodeAuthenticationProvider(
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger, AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
OAuth2UserService userService,
JwtDecoderRegistry jwtDecoderRegistry) { JwtDecoderRegistry jwtDecoderRegistry) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
Assert.notNull(userService, "userService cannot be null");
Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null"); Assert.notNull(jwtDecoderRegistry, "jwtDecoderRegistry cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
this.userService = userService;
this.jwtDecoderRegistry = jwtDecoderRegistry; this.jwtDecoderRegistry = jwtDecoderRegistry;
} }
@ -136,7 +156,16 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
clientAuthentication.getAccessToken(), clientAuthentication.getAccessToken(),
clientAuthentication.getClientRegistration()); clientAuthentication.getClientRegistration());
return clientAuthentication; OAuth2User oauth2User = this.userService.loadUser(clientAuthentication);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(
oauth2User, mappedAuthorities, clientAuthentication);
authenticationResult.setDetails(clientAuthentication.getDetails());
return authenticationResult;
} }
public final void setAccessTokenRepository(SecurityTokenRepository<AccessToken> accessTokenRepository) { public final void setAccessTokenRepository(SecurityTokenRepository<AccessToken> accessTokenRepository) {
@ -144,6 +173,11 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
this.accessTokenRepository = accessTokenRepository; this.accessTokenRepository = accessTokenRepository;
} }
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
this.authoritiesMapper = authoritiesMapper;
}
@Override @Override
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);

View File

@ -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.oidc.client.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationToken;
import org.springframework.security.oauth2.oidc.core.user.OidcUser;
import java.util.Collection;
/**
* An {@link OAuth2UserAuthenticationToken} that represents an
* <i>OpenID Connect 1.0 User</i> {@link Authentication}.
*
* <p>
* This {@link Authentication} associates an {@link OidcUser} principal to an
* {@link OidcClientAuthenticationToken} which represents the <i>&quot;Authorized Client&quot;</i>.
*
* @author Joe Grandja
* @since 5.0
* @see OidcUser
* @see OidcClientAuthenticationToken
* @see OAuth2UserAuthenticationToken
*/
public class OidcUserAuthenticationToken extends OAuth2UserAuthenticationToken {
public OidcUserAuthenticationToken(OidcUser principal, Collection<? extends GrantedAuthority> authorities,
OidcClientAuthenticationToken clientAuthentication) {
super(principal, authorities, clientAuthentication);
}
}

View File

@ -1,104 +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.client.authentication.userinfo;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
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.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserService;
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.OidcUserAuthenticationToken;
import org.springframework.security.oauth2.oidc.core.user.OidcUser;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* An implementation of an {@link AuthenticationProvider} that is responsible
* for obtaining the user attributes of the <i>End-User</i> (resource owner)
* from the <i>UserInfo Endpoint</i> and creating a <code>Principal</code>
* in the form of an {@link OidcUser}.
*
* <p>
* The {@link OidcUserAuthenticationProvider} uses an {@link OidcUserService}
* for loading the {@link OidcUser} and then associating it
* to the returned {@link OidcUserAuthenticationToken}.
*
* @author Joe Grandja
* @since 5.0
* @see OidcUserAuthenticationToken
* @see OidcClientAuthenticationToken
* @see OidcUserService
* @see OidcUser
*/
public class OidcUserAuthenticationProvider implements AuthenticationProvider {
private final OAuth2UserService userService;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public OidcUserAuthenticationProvider(OAuth2UserService userService) {
Assert.notNull(userService, "userService cannot be null");
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OidcClientAuthenticationToken clientAuthentication = (OidcClientAuthenticationToken) authentication;
if (this.userAuthenticated()) {
// Create a new user authentication (using same principal)
// but with a different client authentication association
OidcUserAuthenticationToken currentUserAuthentication =
(OidcUserAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
return new OidcUserAuthenticationToken(
(OidcUser) currentUserAuthentication.getPrincipal(),
currentUserAuthentication.getAuthorities(),
clientAuthentication);
}
OAuth2User oauth2User = this.userService.loadUser(clientAuthentication);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OidcUserAuthenticationToken authenticationResult = new OidcUserAuthenticationToken(
(OidcUser)oauth2User, mappedAuthorities, clientAuthentication);
authenticationResult.setDetails(clientAuthentication.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return OidcClientAuthenticationToken.class.isAssignableFrom(authentication);
}
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
this.authoritiesMapper = authoritiesMapper;
}
private boolean userAuthenticated() {
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
return currentAuthentication != null &&
currentAuthentication instanceof OidcUserAuthenticationToken &&
currentAuthentication.isAuthenticated();
}
}

View File

@ -29,7 +29,7 @@ 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.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationToken; 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;
import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.AccessToken;
@ -100,7 +100,7 @@ public class OAuth2LoginAuthenticationFilterTests {
ClientRegistration clientRegistration = TestUtil.githubClientRegistration(); ClientRegistration clientRegistration = TestUtil.githubClientRegistration();
OAuth2ClientAuthenticationToken clientAuthentication = new OAuth2ClientAuthenticationToken( OAuth2ClientAuthenticationToken clientAuthentication = new OAuth2ClientAuthenticationToken(
clientRegistration, mock(AccessToken.class)); clientRegistration, mock(AccessToken.class));
OAuth2UserAuthenticationToken userAuthentication = new OAuth2UserAuthenticationToken( OAuth2AuthenticationToken userAuthentication = new OAuth2AuthenticationToken(
mock(OAuth2User.class), AuthorityUtils.createAuthorityList("ROLE_USER"), clientAuthentication); mock(OAuth2User.class), AuthorityUtils.createAuthorityList("ROLE_USER"), clientAuthentication);
SecurityContextHolder.getContext().setAuthentication(userAuthentication); SecurityContextHolder.getContext().setAuthentication(userAuthentication);
AuthenticationManager authenticationManager = mock(AuthenticationManager.class); AuthenticationManager authenticationManager = mock(AuthenticationManager.class);

View File

@ -17,7 +17,7 @@ package sample.web;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2UserAuthenticationToken; import org.springframework.security.oauth2.client.authentication.userinfo.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -38,14 +38,14 @@ import java.util.Map;
public class MainController { public class MainController {
@RequestMapping("/") @RequestMapping("/")
public String index(Model model, @AuthenticationPrincipal OAuth2User user, OAuth2UserAuthenticationToken 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.getClientAuthentication().getClientRegistration().getClientName());
return "index"; return "index";
} }
@RequestMapping("/userinfo") @RequestMapping("/userinfo")
public String userinfo(Model model, OAuth2UserAuthenticationToken authentication) { public String userinfo(Model model, OAuth2AuthenticationToken authentication) {
Map userAttributes = Collections.emptyMap(); Map userAttributes = Collections.emptyMap();
String userInfoEndpointUri = authentication.getClientAuthentication().getClientRegistration() String userInfoEndpointUri = authentication.getClientAuthentication().getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri(); .getProviderDetails().getUserInfoEndpoint().getUri();
@ -63,7 +63,7 @@ public class MainController {
return "userinfo"; return "userinfo";
} }
private ExchangeFilterFunction oauth2Credentials(OAuth2UserAuthenticationToken authentication) { private ExchangeFilterFunction oauth2Credentials(OAuth2AuthenticationToken authentication) {
return ExchangeFilterFunction.ofRequestProcessor( return ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> { clientRequest -> {
ClientRequest authorizedRequest = ClientRequest.from(clientRequest) ClientRequest authorizedRequest = ClientRequest.from(clientRequest)