diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClient.java deleted file mode 100644 index e4b711bde6..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClient.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2002-2020 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 - * - * https://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 java.io.IOException; -import java.net.URI; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import com.nimbusds.oauth2.sdk.AccessTokenResponse; -import com.nimbusds.oauth2.sdk.AuthorizationCode; -import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant; -import com.nimbusds.oauth2.sdk.AuthorizationGrant; -import com.nimbusds.oauth2.sdk.ErrorObject; -import com.nimbusds.oauth2.sdk.ParseException; -import com.nimbusds.oauth2.sdk.TokenErrorResponse; -import com.nimbusds.oauth2.sdk.TokenRequest; -import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; -import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; -import com.nimbusds.oauth2.sdk.auth.ClientSecretPost; -import com.nimbusds.oauth2.sdk.auth.Secret; -import com.nimbusds.oauth2.sdk.http.HTTPRequest; -import com.nimbusds.oauth2.sdk.id.ClientID; - -import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.util.CollectionUtils; - -/** - * An implementation of an {@link OAuth2AccessTokenResponseClient} that - * "exchanges" an authorization code credential for an access token credential - * at the Authorization Server's Token Endpoint. - * - *

- * NOTE: This implementation uses the Nimbus OAuth 2.0 SDK internally. - * - * @author Joe Grandja - * @since 5.0 - * @see OAuth2AccessTokenResponseClient - * @see OAuth2AuthorizationCodeGrantRequest - * @see OAuth2AccessTokenResponse - * @see Nimbus OAuth 2.0 - * SDK - * @see Section 4.1.3 Access Token Request - * (Authorization Code Grant) - * @see Section 4.1.4 Access Token Response - * (Authorization Code Grant) - * @deprecated Use {@link DefaultAuthorizationCodeTokenResponseClient} - */ -@Deprecated -public class NimbusAuthorizationCodeTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - @Override - public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) { - ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); - // Build the authorization code grant request for the token endpoint - AuthorizationCode authorizationCode = new AuthorizationCode( - authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode()); - URI redirectUri = toURI( - authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri()); - AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri); - URI tokenUri = toURI(clientRegistration.getProviderDetails().getTokenUri()); - // Set the credentials to authenticate the client at the token endpoint - ClientID clientId = new ClientID(clientRegistration.getClientId()); - Secret clientSecret = new Secret(clientRegistration.getClientSecret()); - boolean isPost = ClientAuthenticationMethod.CLIENT_SECRET_POST - .equals(clientRegistration.getClientAuthenticationMethod()) - || ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod()); - ClientAuthentication clientAuthentication = isPost ? new ClientSecretPost(clientId, clientSecret) - : new ClientSecretBasic(clientId, clientSecret); - com.nimbusds.oauth2.sdk.TokenResponse tokenResponse = getTokenResponse(authorizationCodeGrant, tokenUri, - clientAuthentication); - if (!tokenResponse.indicatesSuccess()) { - TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse; - ErrorObject errorObject = tokenErrorResponse.getErrorObject(); - throw new OAuth2AuthorizationException(getOAuthError(errorObject)); - } - AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse; - String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue(); - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue() - .equalsIgnoreCase(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime(); - // As per spec, in section 5.1 Successful Access Token Response - // https://tools.ietf.org/html/rfc6749#section-5.1 - // If AccessTokenResponse.scope is empty, then default to the scope - // originally requested by the client in the Authorization Request - Set scopes = getScopes(authorizationGrantRequest, accessTokenResponse); - String refreshToken = null; - if (accessTokenResponse.getTokens().getRefreshToken() != null) { - refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); - } - Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); - // @formatter:off - return OAuth2AccessTokenResponse.withToken(accessToken) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - } - - private com.nimbusds.oauth2.sdk.TokenResponse getTokenResponse(AuthorizationGrant authorizationCodeGrant, - URI tokenUri, ClientAuthentication clientAuthentication) { - try { - // Send the Access Token request - TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant); - HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); - httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE); - httpRequest.setConnectTimeout(30000); - httpRequest.setReadTimeout(30000); - return com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send()); - } - catch (ParseException | IOException ex) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, - "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " - + ex.getMessage(), - null); - throw new OAuth2AuthorizationException(oauth2Error, ex); - } - } - - private Set getScopes(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest, - AccessTokenResponse accessTokenResponse) { - if (CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) { - return new LinkedHashSet<>( - authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes()); - } - return new LinkedHashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList()); - } - - private OAuth2Error getOAuthError(ErrorObject errorObject) { - if (errorObject == null) { - return new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR); - } - String errorCode = (errorObject.getCode() != null) ? errorObject.getCode() : OAuth2ErrorCodes.SERVER_ERROR; - String description = errorObject.getDescription(); - String uri = (errorObject.getURI() != null) ? errorObject.getURI().toString() : null; - return new OAuth2Error(errorCode, description, uri); - } - - private static URI toURI(String uriStr) { - try { - return new URI(uriStr); - } - catch (Exception ex) { - throw new IllegalArgumentException("An error occurred parsing URI: " + uriStr, ex); - } - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClientTests.java deleted file mode 100644 index d65c91f471..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClientTests.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2002-2020 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 - * - * https://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 java.time.Instant; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -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.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link NimbusAuthorizationCodeTokenResponseClient}. - * - * @author Joe Grandja - */ -public class NimbusAuthorizationCodeTokenResponseClientTests { - - private ClientRegistration.Builder clientRegistrationBuilder; - - private OAuth2AuthorizationRequest authorizationRequest; - - private OAuth2AuthorizationResponse authorizationResponse; - - private OAuth2AuthorizationExchange authorizationExchange; - - private NimbusAuthorizationCodeTokenResponseClient tokenResponseClient = new NimbusAuthorizationCodeTokenResponseClient(); - - @BeforeEach - public void setUp() { - this.clientRegistrationBuilder = TestClientRegistrations.clientRegistration() - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - this.authorizationRequest = TestOAuth2AuthorizationRequests.request().build(); - this.authorizationResponse = TestOAuth2AuthorizationResponses.success().build(); - this.authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, - this.authorizationResponse); - } - - @Test - public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"; - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(accessTokenSuccessResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient - .getTokenResponse(new OAuth2AuthorizationCodeGrantRequest(this.clientRegistrationBuilder.build(), - this.authorizationExchange)); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - server.shutdown(); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); - assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); - } - - @Test - public void getTokenResponseWhenRedirectUriMalformedThenThrowIllegalArgumentException() { - String redirectUri = "http:\\example.com"; - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .redirectUri(redirectUri).build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, - this.authorizationResponse); - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), authorizationExchange))); - } - - @Test - public void getTokenResponseWhenTokenUriMalformedThenThrowIllegalArgumentException() { - String tokenUri = "http:\\provider.com\\oauth2\\token"; - this.clientRegistrationBuilder.tokenUri(tokenUri); - assertThatIllegalArgumentException() - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))); - } - - @Test - public void getTokenResponseWhenSuccessResponseInvalidThenThrowOAuth2AuthorizationException() throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n"; - // "}\n"; // Make the JSON invalid/malformed - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(accessTokenSuccessResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - try { - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))) - .withMessageContaining("invalid_token_response"); - } - finally { - server.shutdown(); - } - } - - @Test - public void getTokenResponseWhenTokenUriInvalidThenThrowOAuth2AuthorizationException() { - String tokenUri = "https://invalid-provider.com/oauth2/token"; - this.clientRegistrationBuilder.tokenUri(tokenUri); - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))); - } - - @Test - public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenErrorResponse = "{\n" - + " \"error\": \"unauthorized_client\"\n" - + "}\n"; - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setResponseCode(500).setBody(accessTokenErrorResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - try { - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))) - .withMessageContaining("unauthorized_client"); - } - finally { - server.shutdown(); - } - } - - // gh-5594 - @Test - public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() throws Exception { - MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(500)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - try { - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))) - .withMessageContaining("server_error"); - } - finally { - server.shutdown(); - } - } - - @Test - public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() - throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"not-bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(accessTokenSuccessResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - try { - assertThatExceptionOfType(OAuth2AuthorizationException.class) - .isThrownBy(() -> this.tokenResponseClient.getTokenResponse(new OAuth2AuthorizationCodeGrantRequest( - this.clientRegistrationBuilder.build(), this.authorizationExchange))) - .withMessageContaining("invalid_token_response"); - } - finally { - server.shutdown(); - } - } - - @Test - public void getTokenResponseWhenSuccessResponseIncludesScopeThenReturnAccessTokenResponseUsingResponseScope() - throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\"\n" - + "}\n"; - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(accessTokenSuccessResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .scope("openid", "profile", "email", "address").build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, - this.authorizationResponse); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse( - new OAuth2AuthorizationCodeGrantRequest(this.clientRegistrationBuilder.build(), authorizationExchange)); - server.shutdown(); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - } - - @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseUsingRequestedScope() - throws Exception { - MockWebServer server = new MockWebServer(); - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; - // @formatter:on - server.enqueue(new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(accessTokenSuccessResponse)); - server.start(); - String tokenUri = server.url("/oauth2/token").toString(); - this.clientRegistrationBuilder.tokenUri(tokenUri); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .scope("openid", "profile", "email", "address").build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, - this.authorizationResponse); - OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse( - new OAuth2AuthorizationCodeGrantRequest(this.clientRegistrationBuilder.build(), authorizationExchange)); - server.shutdown(); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile", "email", - "address"); - } - -}