/* * Copyright 2002-2019 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.core.convert.converter.Converter; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.util.Arrays; /** * The default implementation of an {@link OAuth2AccessTokenResponseClient} * for the {@link JwtBearerGrantRequest#JWT_BEARER_GRANT_TYPE jwt-bearer} grant. * This implementation uses a {@link RestOperations} when requesting * an access token credential at the Authorization Server's Token Endpoint. * * @author Joe Grandja * @since 5.2 * @see OAuth2AccessTokenResponseClient * @see JwtBearerGrantRequest * @see OAuth2AccessTokenResponse * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7523#section-2.1">Section 2.1 JWTs as Authorization Grants</a> */ public final class DefaultJwtBearerTokenResponseClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> { private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; private Converter<JwtBearerGrantRequest, RequestEntity<?>> requestEntityConverter = new JwtBearerGrantRequestEntityConverter(); private RestOperations restOperations; public DefaultJwtBearerTokenResponseClient() { RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); this.restOperations = restTemplate; } @Override public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest jwtBearerGrantRequest) { Assert.notNull(jwtBearerGrantRequest, "jwtBearerGrantRequest cannot be null"); RequestEntity<?> request = this.requestEntityConverter.convert(jwtBearerGrantRequest); ResponseEntity<OAuth2AccessTokenResponse> response; try { response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); } catch (RestClientException 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); } OAuth2AccessTokenResponse tokenResponse = response.getBody(); if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { // 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 Token Request tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) .scopes(jwtBearerGrantRequest.getClientRegistration().getScopes()) .build(); } return tokenResponse; } /** * Sets the {@link Converter} used for converting the {@link JwtBearerGrantRequest} * to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request. * * @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request */ public void setRequestEntityConverter(Converter<JwtBearerGrantRequest, RequestEntity<?>> requestEntityConverter) { Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); this.requestEntityConverter = requestEntityConverter; } /** * Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token Response. * * <p> * <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured with the following: * <ol> * <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> * <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> * </ol> * * @param restOperations the {@link RestOperations} used when requesting the Access Token Response */ public void setRestOperations(RestOperations restOperations) { Assert.notNull(restOperations, "restOperations cannot be null"); this.restOperations = restOperations; } }