/* * 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.nimbus; import com.nimbusds.oauth2.sdk.*; 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.authentication.AuthenticationServiceException; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * An implementation of an {@link AuthorizationGrantTokenExchanger} that <i>"exchanges"</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. * * @author Joe Grandja * @since 5.0 * @see AuthorizationCodeAuthenticationToken * @see TokenResponseAttributes * @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<AuthorizationCodeAuthenticationToken> { private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; @Override public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken) throws OAuth2AuthenticationException { ClientRegistration clientRegistration = authorizationCodeAuthenticationToken.getClientRegistration(); // Build the authorization code grant request for the token endpoint AuthorizationCode authorizationCode = new AuthorizationCode(authorizationCodeAuthenticationToken.getAuthorizationCode()); URI redirectUri = this.toURI(clientRegistration.getRedirectUri()); AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri); URI tokenUri = this.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()); ClientAuthentication clientAuthentication; if (ClientAuthenticationMethod.FORM.equals(clientRegistration.getClientAuthenticationMethod())) { clientAuthentication = new ClientSecretPost(clientId, clientSecret); } else { clientAuthentication = new ClientSecretBasic(clientId, clientSecret); } TokenResponse tokenResponse; try { // Send the Access Token request TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant); HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE); tokenResponse = TokenResponse.parse(httpRequest.send()); } catch (ParseException pe) { // This error occurs if the Access Token Response is not well-formed, // for example, a required attribute is missing throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE), pe); } catch (IOException ioe) { // This error occurs when there is a network-related issue throw new AuthenticationServiceException("An error occurred while sending the Access Token Request: " + ioe.getMessage(), ioe); } if (!tokenResponse.indicatesSuccess()) { TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse; ErrorObject errorObject = tokenErrorResponse.getErrorObject(); OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(), (errorObject.getURI() != null ? errorObject.getURI().toString() : null)); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse; String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue(); AccessToken.TokenType accessTokenType = null; if (AccessToken.TokenType.BEARER.value().equals(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) { accessTokenType = AccessToken.TokenType.BEARER; } long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime(); Set<String> scopes = Collections.emptySet(); if (!CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) { scopes = new HashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList()); } Map<String, Object> additionalParameters = accessTokenResponse.getCustomParameters().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); return TokenResponseAttributes.withToken(accessToken) .tokenType(accessTokenType) .expiresIn(expiresIn) .scopes(scopes) .additionalParameters(additionalParameters) .build(); } private URI toURI(String uriStr) { try { return new URI(uriStr); } catch (Exception ex) { throw new IllegalArgumentException("An error occurred parsing URI: " + uriStr, ex); } } }