Re-factor OAuth2AuthorizationCodeAuthenticationToken

Fixes gh-4730
This commit is contained in:
Joe Grandja 2017-10-28 17:10:39 -04:00
parent 64d8c8b8a9
commit 0c68eb1821
13 changed files with 344 additions and 190 deletions

View File

@ -1014,7 +1014,7 @@ public final class HttpSecurity extends
* }
*
* @Bean
* public AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger() {
* public AuthorizationGrantTokenExchanger<OAuth2LoginAuthenticationToken> authorizationCodeTokenExchanger() {
* // Custom implementation that exchanges an "Authorization Code Grant" for an "Access Token"
* return new AuthorizationCodeTokenExchangerImpl();
* }

View File

@ -1,47 +0,0 @@
/*
* Copyright 2002-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;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
import java.util.Collections;
/**
* Base implementation of an {@link AbstractAuthenticationToken} that holds
* an <i>authorization grant</i> credential for a specific {@link AuthorizationGrantType}.
*
* @author Joe Grandja
* @since 5.0
* @see AuthorizationGrantType
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
*/
public abstract class AbstractOAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final AuthorizationGrantType authorizationGrantType;
protected AbstractOAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType) {
super(Collections.emptyList());
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
this.authorizationGrantType = authorizationGrantType;
}
public AuthorizationGrantType getGrantType() {
return this.authorizationGrantType;
}
}

View File

@ -19,7 +19,6 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
@ -30,34 +29,33 @@ import java.util.Collection;
* that represents an <i>OAuth 2.0</i> {@link Authentication}.
* <p>
* This {@link Authentication} associates an {@link OAuth2User} <code>Principal</code>
* to an {@link OAuth2AuthorizedClient}, which the End-User (Principal) granted authorization to
* to the identifier of the {@link #getAuthorizedClientRegistrationId() Authorized Client},
* which the End-User (Principal) granted authorization to
* so that it can access its protected resource(s) at the <i>UserInfo Endpoint</i>.
*
* @author Joe Grandja
* @since 5.0
* @see AbstractAuthenticationToken
* @see OAuth2AuthorizedClient
* @see OAuth2User
*
* @param <U> The type of <i>OAuth 2.0 User</i>
* @param <C> The type of <i>Authorized Client</i>
*/
public class OAuth2AuthenticationToken<U extends OAuth2User, C extends OAuth2AuthorizedClient> extends AbstractAuthenticationToken {
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final U principal;
private final C authorizedClient;
private final OAuth2User principal;
private final String authorizedClientRegistrationId;
public OAuth2AuthenticationToken(U principal, Collection<? extends GrantedAuthority> authorities, C authorizedClient) {
public OAuth2AuthenticationToken(OAuth2User principal,
Collection<? extends GrantedAuthority> authorities,
String authorizedClientRegistrationId) {
super(authorities);
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.hasText(authorizedClientRegistrationId, "authorizedClientRegistrationId cannot be empty");
this.principal = principal;
this.authorizedClient = authorizedClient;
this.authorizedClientRegistrationId = authorizedClientRegistrationId;
this.setAuthenticated(true);
}
@Override
public U getPrincipal() {
public OAuth2User getPrincipal() {
return this.principal;
}
@ -67,7 +65,7 @@ public class OAuth2AuthenticationToken<U extends OAuth2User, C extends OAuth2Aut
return "";
}
public C getAuthorizedClient() {
return this.authorizedClient;
public String getAuthorizedClientRegistrationId() {
return this.authorizedClientRegistrationId;
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2002-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;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.util.Assert;
/**
* An implementation of an {@link AbstractOAuth2AuthorizationGrantAuthenticationToken} that holds
* an <i>authorization code grant</i> credential for a specific client identified in {@link #getClientRegistration()}.
*
* @author Joe Grandja
* @since 5.0
* @see AbstractOAuth2AuthorizationGrantAuthenticationToken
* @see ClientRegistration
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationResponse
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.1">Section 1.3.1 Authorization Code Grant</a>
*/
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractOAuth2AuthorizationGrantAuthenticationToken {
private final ClientRegistration clientRegistration;
private final OAuth2AuthorizationExchange authorizationExchange;
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange) {
super(AuthorizationGrantType.AUTHORIZATION_CODE);
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
this.clientRegistration = clientRegistration;
this.authorizationExchange = authorizationExchange;
this.setAuthenticated(false);
}
@Override
public Object getPrincipal() {
return "";
}
@Override
public Object getCredentials() {
return "";
}
public ClientRegistration getClientRegistration() {
return this.clientRegistration;
}
public OAuth2AuthorizationExchange getAuthorizationExchange() {
return this.authorizationExchange;
}
}

View File

@ -20,7 +20,6 @@ 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.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
@ -37,12 +36,12 @@ import org.springframework.util.Assert;
import java.util.Collection;
/**
* An implementation of an {@link AuthenticationProvider}
* for the <i>OAuth 2.0 Authorization Code Grant Flow</i>.
* An implementation of an {@link AuthenticationProvider} for <i>OAuth 2.0 Login</i>,
* which leverages the <i>OAuth 2.0 Authorization Code Grant</i> Flow.
*
* This {@link AuthenticationProvider} is responsible for authenticating
* 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.
* 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.
* <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}
@ -50,10 +49,9 @@ import java.util.Collection;
*
* @author Joe Grandja
* @since 5.0
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2AuthenticationToken
* @see OAuth2LoginAuthenticationToken
* @see AuthorizationGrantTokenExchanger
* @see OAuth2UserService
* @see OAuth2AuthorizedClient
* @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.3">Section 4.1.3 Access Token Request</a>
@ -78,8 +76,8 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
(OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
@ -124,14 +122,15 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
OAuth2User oauth2User = this.userService.loadUser(
new OAuth2UserRequest(authorizationCodeAuthentication.getClientRegistration(), accessToken));
OAuth2AuthorizedClient oauth2AuthorizedClient = new OAuth2AuthorizedClient(
authorizationCodeAuthentication.getClientRegistration(), oauth2User.getName(), accessToken);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2AuthenticationToken<OAuth2User, OAuth2AuthorizedClient> authenticationResult =
new OAuth2AuthenticationToken<>(oauth2User, mappedAuthorities, oauth2AuthorizedClient);
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
oauth2User,
mappedAuthorities,
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(),
accessToken);
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
@ -144,6 +143,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,116 @@
/*
* 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;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import java.util.Collection;
import java.util.Collections;
/**
* An {@link AbstractAuthenticationToken} for <i>OAuth 2.0 Login</i>,
* which leverages the <i>OAuth 2.0 Authorization Code Grant</i> Flow.
*
* @author Joe Grandja
* @since 5.0
* @see AbstractAuthenticationToken
* @see OAuth2User
* @see ClientRegistration
* @see OAuth2AuthorizationExchange
* @see OAuth2AccessToken
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
*/
public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private OAuth2User principal;
private ClientRegistration clientRegistration;
private OAuth2AuthorizationExchange authorizationExchange;
private OAuth2AccessToken accessToken;
/**
* This constructor should be used when the Authorization Request/Response is complete.
*
* @param clientRegistration
* @param authorizationExchange
*/
public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange) {
super(Collections.emptyList());
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
this.clientRegistration = clientRegistration;
this.authorizationExchange = authorizationExchange;
this.setAuthenticated(false);
}
/**
* This constructor should be used when the Access Token Request/Response is complete,
* which indicates that the Authorization Code Grant flow has fully completed
* and OAuth 2.0 Login has been achieved.
*
* @param principal
* @param authorities
* @param clientRegistration
* @param authorizationExchange
* @param accessToken
*/
public OAuth2LoginAuthenticationToken(OAuth2User principal,
Collection<? extends GrantedAuthority> authorities,
ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange,
OAuth2AccessToken accessToken) {
super(authorities);
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
Assert.notNull(accessToken, "accessToken cannot be null");
this.principal = principal;
this.clientRegistration = clientRegistration;
this.authorizationExchange = authorizationExchange;
this.accessToken = accessToken;
this.setAuthenticated(true);
}
@Override
public OAuth2User getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return "";
}
public ClientRegistration getClientRegistration() {
return this.clientRegistration;
}
public OAuth2AuthorizationExchange getAuthorizationExchange() {
return this.authorizationExchange;
}
public OAuth2AccessToken getAccessToken() {
return this.accessToken;
}
}

View File

@ -20,12 +20,10 @@ 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.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.jwt.JwtDecoderRegistry;
import org.springframework.security.oauth2.client.oidc.OidcAuthorizedClient;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@ -55,19 +53,18 @@ import java.util.List;
* for the <i>OpenID Connect Core 1.0 Authorization Code Grant Flow</i>.
* <p>
* This {@link AuthenticationProvider} is responsible for authenticating
* 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.
* 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.
* <p>
* It will also obtain the user attributes of the <i>End-User</i> (resource owner)
* 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
* @since 5.0
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2AuthenticationToken
* @see OidcAuthorizationCodeAuthenticationToken
* @see AuthorizationGrantTokenExchanger
* @see OidcUserService
* @see OidcAuthorizedClient
* @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#TokenRequest">Section 3.1.3.1 Token Request</a>
@ -97,8 +94,8 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
(OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request - http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
@ -160,14 +157,16 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
OidcUser oidcUser = this.userService.loadUser(
new OidcUserRequest(clientRegistration, accessToken, idToken));
OidcAuthorizedClient oidcAuthorizedClient = new OidcAuthorizedClient(
clientRegistration, oidcUser.getName(), accessToken, idToken);
Collection<? extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oidcUser.getAuthorities());
OAuth2AuthenticationToken<OidcUser, OidcAuthorizedClient> authenticationResult =
new OAuth2AuthenticationToken<>(oidcUser, mappedAuthorities, oidcAuthorizedClient);
OidcAuthorizationCodeAuthenticationToken authenticationResult = new OidcAuthorizationCodeAuthenticationToken(
oidcUser,
mappedAuthorities,
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(),
accessToken,
idToken);
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
@ -180,7 +179,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
}
private void validateIdToken(OidcIdToken idToken, ClientRegistration clientRegistration) {

View File

@ -0,0 +1,81 @@
/*
* 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.oidc.authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* An {@link OAuth2LoginAuthenticationToken} for <i>OpenID Connect 1.0 Authentication</i>,
* which leverages the <i>Authorization Code Flow</i>.
*
* @author Joe Grandja
* @since 5.0
* @see OAuth2LoginAuthenticationToken
* @see OidcIdToken
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">3.1 Authorization Code Flow</a>
*/
public class OidcAuthorizationCodeAuthenticationToken extends OAuth2LoginAuthenticationToken {
private OidcIdToken idToken;
/**
* This constructor should be used when the Authentication Request/Response is complete.
*
* @param clientRegistration
* @param authorizationExchange
*/
public OidcAuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange) {
super(clientRegistration, authorizationExchange);
}
/**
* This constructor should be used when the Token Request/Response is complete,
* which indicates that the Authorization Code Flow has fully completed
* and OpenID Connect 1.0 Authentication has been achieved.
*
* @param principal
* @param authorities
* @param clientRegistration
* @param authorizationExchange
* @param accessToken
* @param idToken
*/
public OidcAuthorizationCodeAuthenticationToken(OidcUser principal,
Collection<? extends GrantedAuthority> authorities,
ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange,
OAuth2AccessToken accessToken,
OidcIdToken idToken) {
super(principal, authorities, clientRegistration, authorizationExchange, accessToken);
Assert.notNull(idToken, "idToken cannot be null");
this.idToken = idToken;
}
public OidcIdToken getIdToken() {
return this.idToken;
}
}

View File

@ -21,8 +21,8 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository;
@ -35,7 +35,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -60,7 +59,7 @@ import java.io.IOException;
* and redirect the end-user's user-agent back to this <code>Filter</code> (the client).
* </li>
* <li>
* This <code>Filter</code> will then create an {@link OAuth2AuthorizationCodeAuthenticationToken} with
* This <code>Filter</code> will then create an {@link OAuth2LoginAuthenticationToken} with
* the {@link OAuth2ParameterNames#CODE} received in the previous step and delegate it to
* {@link OAuth2LoginAuthenticationProvider#authenticate(Authentication)} (indirectly via {@link AuthenticationManager}).
* </li>
@ -69,7 +68,7 @@ import java.io.IOException;
* @author Joe Grandja
* @since 5.0
* @see AbstractAuthenticationProcessingFilter
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2LoginAuthenticationToken
* @see OAuth2AuthenticationToken
* @see OAuth2LoginAuthenticationProvider
* @see OAuth2AuthorizationRequest
@ -136,19 +135,29 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
.redirectUri(authorizationRequest.getRedirectUri())
.build();
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = new OAuth2AuthorizationCodeAuthenticationToken(
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authorizationCodeAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request));
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
OAuth2AuthenticationToken<OAuth2User, OAuth2AuthorizedClient> oauth2Authentication =
(OAuth2AuthenticationToken<OAuth2User, OAuth2AuthorizedClient>) this.getAuthenticationManager().authenticate(authorizationCodeAuthentication);
OAuth2LoginAuthenticationToken authenticationResult =
(OAuth2LoginAuthenticationToken)this.getAuthenticationManager().authenticate(authenticationRequest);
OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(
authenticationResult.getPrincipal(),
authenticationResult.getAuthorities(),
authenticationResult.getClientRegistration().getRegistrationId());
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(),
oauth2Authentication.getName(),
authenticationResult.getAccessToken());
this.authorizedClientService.saveAuthorizedClient(
oauth2Authentication.getAuthorizedClient(), oauth2Authentication);
authorizedClient, oauth2Authentication);
this.accessTokenRepository.saveToken(
oauth2Authentication.getAuthorizedClient().getAccessToken(),
oauth2Authentication.getAuthorizedClient().getClientRegistration(),
authorizedClient.getAccessToken(),
authorizedClient.getClientRegistration(),
oauth2Authentication);
return oauth2Authentication;

View File

@ -27,9 +27,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -48,6 +48,7 @@ import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests {@link OAuth2LoginAuthenticationFilter}.
@ -99,13 +100,18 @@ public class OAuth2LoginAuthenticationFilterTests {
@Test
public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSuccessHandlerIsCalled() throws Exception {
ClientRegistration clientRegistration = TestUtil.githubClientRegistration();
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
clientRegistration, "principal", mock(OAuth2AccessToken.class));
OAuth2User oauth2User = mock(OAuth2User.class);
when(oauth2User.getName()).thenReturn("principal name");
OAuth2LoginAuthenticationToken loginAuthentication = mock(OAuth2LoginAuthenticationToken.class);
when(loginAuthentication.getPrincipal()).thenReturn(oauth2User);
when(loginAuthentication.getClientRegistration()).thenReturn(clientRegistration);
when(loginAuthentication.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
OAuth2AuthenticationToken userAuthentication = new OAuth2AuthenticationToken(
mock(OAuth2User.class), AuthorityUtils.createAuthorityList("ROLE_USER"), authorizedClient);
oauth2User, AuthorityUtils.NO_AUTHORITIES, clientRegistration.getRegistrationId());
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
Mockito.when(authenticationManager.authenticate(Matchers.any(Authentication.class))).thenReturn(userAuthentication);
when(authenticationManager.authenticate(Matchers.any(Authentication.class))).thenReturn(loginAuthentication);
OAuth2LoginAuthenticationFilter filter = Mockito.spy(setupFilter(authenticationManager, clientRegistration));
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);

View File

@ -30,12 +30,16 @@ import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@ -388,5 +392,13 @@ public class OAuth2LoginApplicationTests {
@EnableAutoConfiguration
@ComponentScan(basePackages = "sample.web")
public static class SpringBootApplicationTestConfig {
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public OAuth2AuthorizedClientService<OAuth2AuthorizedClient> authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService<>(this.clientRegistrationRepository);
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-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 sample.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
/**
* @author Joe Grandja
*/
@Configuration
public class OAuth2LoginConfig {
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public OAuth2AuthorizedClientService<OAuth2AuthorizedClient> authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService<>(this.clientRegistrationRepository);
}
}

View File

@ -15,10 +15,11 @@
*/
package sample.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
@ -37,21 +38,26 @@ import java.util.Map;
@Controller
public class MainController {
@Autowired
private OAuth2AuthorizedClientService authorizedClientService;
@RequestMapping("/")
public String index(Model model, @AuthenticationPrincipal OAuth2User user, OAuth2AuthenticationToken authentication) {
model.addAttribute("userName", user.getName());
model.addAttribute("clientName", authentication.getAuthorizedClient().getClientRegistration().getClientName());
public String index(Model model, OAuth2AuthenticationToken authentication) {
OAuth2AuthorizedClient authorizedClient = this.getAuthorizedClient(authentication);
model.addAttribute("userName", authentication.getName());
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
return "index";
}
@RequestMapping("/userinfo")
public String userinfo(Model model, OAuth2AuthenticationToken authentication) {
OAuth2AuthorizedClient authorizedClient = this.getAuthorizedClient(authentication);
Map userAttributes = Collections.emptyMap();
String userInfoEndpointUri = authentication.getAuthorizedClient().getClientRegistration()
String userInfoEndpointUri = authorizedClient.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri();
if (!StringUtils.isEmpty(userInfoEndpointUri)) { // userInfoEndpointUri is optional for OIDC Clients
userAttributes = WebClient.builder()
.filter(oauth2Credentials(authentication))
.filter(oauth2Credentials(authorizedClient))
.build()
.get()
.uri(userInfoEndpointUri)
@ -63,11 +69,16 @@ public class MainController {
return "userinfo";
}
private ExchangeFilterFunction oauth2Credentials(OAuth2AuthenticationToken authentication) {
private OAuth2AuthorizedClient getAuthorizedClient(OAuth2AuthenticationToken authentication) {
return this.authorizedClientService.loadAuthorizedClient(
authentication.getAuthorizedClientRegistrationId(), authentication);
}
private ExchangeFilterFunction oauth2Credentials(OAuth2AuthorizedClient authorizedClient) {
return ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> {
ClientRequest authorizedRequest = ClientRequest.from(clientRequest)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authentication.getAuthorizedClient().getAccessToken().getTokenValue())
.header(HttpHeaders.AUTHORIZATION, "Bearer " + authorizedClient.getAccessToken().getTokenValue())
.build();
return Mono.just(authorizedRequest);
});