| 
					
				 | 
			
			
				@@ -15,38 +15,21 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 package org.springframework.security.oauth2.client.endpoint; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import java.util.LinkedHashMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import java.util.LinkedHashSet; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import java.util.Map; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import java.util.Set; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import org.springframework.core.ParameterizedTypeReference; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.http.MediaType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.oauth2.client.registration.ClientRegistration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.oauth2.core.AuthorizationGrantType; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-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; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import org.springframework.util.CollectionUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.web.reactive.function.BodyInserters; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.web.reactive.function.client.ExchangeFilterFunctions; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.web.reactive.function.client.WebClient; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.AccessTokenResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.ErrorObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.ParseException; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.TokenErrorResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.TokenResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import com.nimbusds.oauth2.sdk.token.AccessToken; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import net.minidev.json.JSONObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import reactor.core.publisher.Mono; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  * An implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  * an authorization code credential for an access token credential 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -65,8 +48,6 @@ import reactor.core.publisher.Mono; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  * @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 NimbusReactiveAuthorizationCodeTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	private WebClient webClient = WebClient.builder() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			.filter(ExchangeFilterFunctions.basicAuthentication()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			.build(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -87,52 +68,15 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClient implements React 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 					.accept(MediaType.APPLICATION_JSON) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 					.attributes(basicAuthenticationCredentials(clientRegistration.getClientId(), clientRegistration.getClientSecret())) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 					.body(body) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.retrieve() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.onStatus(s -> false, response -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						throw new IllegalStateException("Disabled Status Handlers"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.map(json -> parse(json)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.flatMap(tokenResponse -> accessTokenResponse(tokenResponse)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.map(accessTokenResponse -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						OAuth2AccessToken.TokenType accessTokenType = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								accessToken.getType().getValue())) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-							accessTokenType = OAuth2AccessToken.TokenType.BEARER; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						long expiresIn = accessToken.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<String> scopes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						if (CollectionUtils.isEmpty( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								accessToken.getScope())) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-							scopes = new LinkedHashSet<>( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-									authorizationExchange.getAuthorizationRequest().getScopes()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-							scopes = new LinkedHashSet<>( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-									accessToken.getScope().toStringList()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						String refreshToken = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						if (accessTokenResponse.getTokens().getRefreshToken() != null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-							refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						Map<String, Object> additionalParameters = new LinkedHashMap<>( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								accessTokenResponse.getCustomParameters()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-						return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								.tokenType(accessTokenType) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								.expiresIn(expiresIn) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								.scopes(scopes) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								.refreshToken(refreshToken) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-								.additionalParameters(additionalParameters) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					.exchange() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					.flatMap(response -> response.body(oauth2AccessTokenResponse())) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					.map(response -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+						if (response.getAccessToken().getScopes().isEmpty()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+							response = OAuth2AccessTokenResponse.withResponse(response) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+								.scopes(authorizationExchange.getAuthorizationRequest().getScopes()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 								.build(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+						} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+						return response; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 					}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -148,30 +92,4 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClient implements React 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		return body; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	private static Mono<AccessTokenResponse> accessTokenResponse(TokenResponse tokenResponse) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		if (tokenResponse.indicatesSuccess()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			return Mono.just(tokenResponse) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					.cast(AccessTokenResponse.class); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		ErrorObject errorObject = tokenErrorResponse.getErrorObject(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				errorObject.getDescription(), (errorObject.getURI() != null ? 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				errorObject.getURI().toString() : 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				null)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		return Mono.error(new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString())); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	private static TokenResponse parse(Map<String, String> json) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			return TokenResponse.parse(new JSONObject(json)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		catch (ParseException pe) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					"An error occurred parsing the Access Token response: " + pe.getMessage(), null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), pe); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |