| 
					
				 | 
			
			
				@@ -1071,6 +1071,94 @@ public class OAuth2AuthorizationCodeGrantTests { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			.isEqualTo(true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	// gh-2182 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	@Test 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.clear(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.add("message.read"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.add("message.write"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.registeredClientRepository.save(registeredClient); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		MvcResult mvcResult = this.mvc 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(status().isCreated()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(jsonPath("$.request_uri").isNotEmpty()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(jsonPath("$.expires_in").isNotEmpty()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andReturn(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String consentPage = this.mvc 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.with(user("user"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(status().is2xxSuccessful()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andReturn() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.getResponse() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.getContentAsString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(consentPage).contains("Consent required"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(consentPage).contains(scopeCheckbox("message.read")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(consentPage).contains(scopeCheckbox("message.write")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	// gh-2182 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	@Test 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.autowire(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.clear(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.add("message.read"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			scopes.add("message.write"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.registeredClientRepository.save(registeredClient); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		MvcResult mvcResult = this.mvc 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(status().isCreated()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(jsonPath("$.request_uri").isNotEmpty()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(jsonPath("$.expires_in").isNotEmpty()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andReturn(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		mvcResult = this.mvc 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				.with(user("user"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andExpect(status().is3xxRedirection()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.andReturn(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		MultiValueMap<String, String> redirectQueryParams = uriComponents.getQueryParams(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(uriComponents.getPath()).isEqualTo(consentPage); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			.isEqualTo(registeredClient.getClientId()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		String state = extractParameterFromRedirectUri(redirectedUrl, "state"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		assertThat(authorization).isNotNull(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		Map<String, Object> additionalParameters = new HashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1125,8 +1213,8 @@ public class OAuth2AuthorizationCodeGrantTests { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		String clientId = registeredClient.getClientId(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		String clientSecret = registeredClient.getClientSecret(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		String credentialsString = clientId + ":" + clientSecret; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1496,4 +1584,28 @@ public class OAuth2AuthorizationCodeGrantTests { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	@EnableWebSecurity 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	@Configuration(proxyBeanMethods = false) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			extends AuthorizationServerConfiguration { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// @formatter:off 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		@Bean 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			http 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					.oauth2AuthorizationServer((authorizationServer) -> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+							authorizationServer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+									.pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+									.authorizationEndpoint((authorizationEndpoint) -> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+											authorizationEndpoint.consentPage(consentPage)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					.authorizeHttpRequests((authorize) -> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+							authorize.anyRequest().authenticated() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			return http.build(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// @formatter:on 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |