Re-factor AuthorizationGrantTokenExchanger

Fixes gh-4728
This commit is contained in:
Joe Grandja 2017-10-28 12:56:28 -04:00
parent 16e69d06b4
commit 64d8c8b8a9
9 changed files with 143 additions and 38 deletions

View File

@ -62,7 +62,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.AuthorizationRequestUriBuilder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;

View File

@ -24,9 +24,9 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
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.authentication.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.authentication.NimbusAuthorizationCodeTokenExchanger;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.endpoint.AuthorizationRequestUriBuilder;
import org.springframework.security.oauth2.client.jwt.JwtDecoderRegistry;
@ -144,7 +144,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
}
public class TokenEndpointConfig {
private AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger;
private OAuth2TokenRepository<OAuth2AccessToken> accessTokenRepository;
private JwtDecoderRegistry jwtDecoderRegistry;
@ -152,7 +152,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
}
public TokenEndpointConfig authorizationCodeTokenExchanger(
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger) {
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger;
@ -237,7 +237,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
public void init(B http) throws Exception {
super.init(http);
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger =
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger =
this.tokenEndpointConfig.authorizationCodeTokenExchanger;
if (authorizationCodeTokenExchanger == null) {
authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger();

View File

@ -21,6 +21,8 @@ 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;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -60,12 +62,12 @@ import java.util.Collection;
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
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 final AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger;
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public OAuth2LoginAuthenticationProvider(
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null");
@ -110,7 +112,10 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
}
OAuth2AccessTokenResponse accessTokenResponse =
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
this.authorizationCodeTokenExchanger.exchange(
new OAuth2AuthorizationCodeGrantRequest(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
OAuth2AccessToken accessToken = new OAuth2AccessToken(accessTokenResponse.getTokenType(),
accessTokenResponse.getTokenValue(), accessTokenResponse.getIssuedAt(),

View File

@ -0,0 +1,42 @@
/*
* 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.endpoint;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
/**
* Base implementation of an <i>OAuth 2.0 Authorization Grant</i> request
* that holds an <i>authorization grant</i> credential and is used when
* initiating a HTTP request to the Authorization Server's <i>Token Endpoint</i>.
*
* @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 AbstractOAuth2AuthorizationGrantRequest {
private final AuthorizationGrantType authorizationGrantType;
protected AbstractOAuth2AuthorizationGrantRequest(AuthorizationGrantType authorizationGrantType) {
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
this.authorizationGrantType = authorizationGrantType;
}
public AuthorizationGrantType getGrantType() {
return this.authorizationGrantType;
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.authentication;
package org.springframework.security.oauth2.client.endpoint;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -21,21 +21,21 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
/**
* Implementations of this interface are responsible for <i>&quot;exchanging&quot;</i>
* an <i>authorization grant</i> credential (for example, an authorization code) for an
* <i>access token</i> credential at the authorization server's <i>Token Endpoint</i>.
* A strategy for <i>&quot;exchanging&quot;</i> an <i>Authorization Grant</i> credential
* (e.g. an Authorization Code) for an <i>Access Token</i> credential
* at the Authorization Server's <i>Token Endpoint</i>.
*
* @author Joe Grandja
* @since 5.0
* @see AuthorizationGrantType
* @see AbstractOAuth2AuthorizationGrantAuthenticationToken
* @see AbstractOAuth2AuthorizationGrantRequest
* @see OAuth2AccessTokenResponse
* @see AuthorizationGrantType
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
*/
public interface AuthorizationGrantTokenExchanger<T extends AbstractOAuth2AuthorizationGrantAuthenticationToken> {
public interface AuthorizationGrantTokenExchanger<T extends AbstractOAuth2AuthorizationGrantRequest> {
OAuth2AccessTokenResponse exchange(T authorizationGrantAuthentication) throws OAuth2AuthenticationException;
OAuth2AccessTokenResponse exchange(T authorizationGrantRequest) throws OAuth2AuthenticationException;
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.client.authentication;
package org.springframework.security.oauth2.client.endpoint;
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
@ -33,8 +33,8 @@ import com.nimbusds.oauth2.sdk.id.ClientID;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
@ -49,8 +49,8 @@ import java.util.Set;
/**
* An implementation of an {@link AuthorizationGrantTokenExchanger} that <i>&quot;exchanges&quot;</i>
* an <i>authorization code</i> credential for an <i>access token</i> credential
* at the authorization server's <i>Token Endpoint</i>.
* an <i>Authorization Code</i> credential for an <i>Access Token</i> credential
* at the Authorization Server's <i>Token Endpoint</i>.
*
* <p>
* <b>NOTE:</b> This implementation uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
@ -58,24 +58,24 @@ import java.util.Set;
* @author Joe Grandja
* @since 5.0
* @see AuthorizationGrantTokenExchanger
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2AuthorizationCodeGrantRequest
* @see OAuth2AccessTokenResponse
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
*/
public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> {
public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> {
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
@Override
public OAuth2AccessTokenResponse exchange(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication)
public OAuth2AccessTokenResponse exchange(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest)
throws OAuth2AuthenticationException {
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
// Build the authorization code grant request for the token endpoint
AuthorizationCode authorizationCode = new AuthorizationCode(
authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationResponse().getCode());
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
URI redirectUri = toURI(clientRegistration.getRedirectUri());
AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri);
URI tokenUri = toURI(clientRegistration.getProviderDetails().getTokenUri());
@ -130,7 +130,7 @@ public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrant
Set<String> scopes;
if (CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) {
scopes = new LinkedHashSet<>(
authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes());
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes());
} else {
scopes = new LinkedHashSet<>(
accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());

View File

@ -0,0 +1,54 @@
/*
* 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.endpoint;
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.util.Assert;
/**
* An <i>OAuth 2.0 Authorization Code Grant</i> request that holds an <i>Authorization Code</i> credential,
* which was granted by the <i>Resource Owner</i> to the specified {@link #getClientRegistration() Client}.
*
* @author Joe Grandja
* @since 5.0
* @see AbstractOAuth2AuthorizationGrantRequest
* @see ClientRegistration
* @see OAuth2AuthorizationExchange
* @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 OAuth2AuthorizationCodeGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
private final ClientRegistration clientRegistration;
private final OAuth2AuthorizationExchange authorizationExchange;
public OAuth2AuthorizationCodeGrantRequest(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;
}
public ClientRegistration getClientRegistration() {
return this.clientRegistration;
}
public OAuth2AuthorizationExchange getAuthorizationExchange() {
return this.authorizationExchange;
}
}

View File

@ -20,9 +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.AuthorizationGrantTokenExchanger;
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;
@ -76,13 +77,13 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
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_ID_TOKEN_ERROR_CODE = "invalid_id_token";
private final AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger;
private final AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger;
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
private final JwtDecoderRegistry jwtDecoderRegistry;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
public OidcAuthorizationCodeAuthenticationProvider(
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger,
AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenExchanger,
OAuth2UserService<OidcUserRequest, OidcUser> userService,
JwtDecoderRegistry jwtDecoderRegistry) {
@ -130,7 +131,10 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
}
OAuth2AccessTokenResponse accessTokenResponse =
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication);
this.authorizationCodeTokenExchanger.exchange(
new OAuth2AuthorizationCodeGrantRequest(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
OAuth2AccessToken accessToken = new OAuth2AccessToken(accessTokenResponse.getTokenType(),
accessTokenResponse.getTokenValue(), accessTokenResponse.getIssuedAt(),

View File

@ -36,17 +36,17 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.endpoint.AuthorizationGrantTokenExchanger;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.test.context.junit4.SpringRunner;
@ -354,7 +354,7 @@ public class OAuth2LoginApplicationTests {
}
// @formatter:on
private AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeAuthenticationToken> mockAuthorizationCodeTokenExchanger() {
private AuthorizationGrantTokenExchanger<OAuth2AuthorizationCodeGrantRequest> mockAuthorizationCodeTokenExchanger() {
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.expiresIn(60 * 1000)