Skip to content

Commit fc795a8

Browse files
committed
PAR uses requested scopes on consent
Issue spring-projects/spring-authorization-server#2182
1 parent 4bc3198 commit fc795a8

File tree

3 files changed

+133
-4
lines changed

3 files changed

+133
-4
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,94 @@ public void requestWhenPushedAuthorizationRequestThenReturnAccessTokenResponse()
10711071
.isEqualTo(true);
10721072
}
10731073

1074+
// gh-2182
1075+
@Test
1076+
public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception {
1077+
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire();
1078+
1079+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
1080+
scopes.clear();
1081+
scopes.add("message.read");
1082+
scopes.add("message.write");
1083+
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
1084+
this.registeredClientRepository.save(registeredClient);
1085+
1086+
MvcResult mvcResult = this.mvc
1087+
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
1088+
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
1089+
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
1090+
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
1091+
.andExpect(status().isCreated())
1092+
.andExpect(jsonPath("$.request_uri").isNotEmpty())
1093+
.andExpect(jsonPath("$.expires_in").isNotEmpty())
1094+
.andReturn();
1095+
1096+
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
1097+
1098+
String consentPage = this.mvc
1099+
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
1100+
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
1101+
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
1102+
.with(user("user")))
1103+
.andExpect(status().is2xxSuccessful())
1104+
.andReturn()
1105+
.getResponse()
1106+
.getContentAsString();
1107+
1108+
assertThat(consentPage).contains("Consent required");
1109+
assertThat(consentPage).contains(scopeCheckbox("message.read"));
1110+
assertThat(consentPage).contains(scopeCheckbox("message.write"));
1111+
}
1112+
1113+
// gh-2182
1114+
@Test
1115+
public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception {
1116+
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class)
1117+
.autowire();
1118+
1119+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
1120+
scopes.clear();
1121+
scopes.add("message.read");
1122+
scopes.add("message.write");
1123+
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
1124+
this.registeredClientRepository.save(registeredClient);
1125+
1126+
MvcResult mvcResult = this.mvc
1127+
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
1128+
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
1129+
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
1130+
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
1131+
.andExpect(status().isCreated())
1132+
.andExpect(jsonPath("$.request_uri").isNotEmpty())
1133+
.andExpect(jsonPath("$.expires_in").isNotEmpty())
1134+
.andReturn();
1135+
1136+
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
1137+
1138+
mvcResult = this.mvc
1139+
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
1140+
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
1141+
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
1142+
.with(user("user")))
1143+
.andExpect(status().is3xxRedirection())
1144+
.andReturn();
1145+
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
1146+
assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+");
1147+
1148+
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
1149+
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
1150+
MultiValueMap<String, String> redirectQueryParams = uriComponents.getQueryParams();
1151+
1152+
assertThat(uriComponents.getPath()).isEqualTo(consentPage);
1153+
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write");
1154+
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID))
1155+
.isEqualTo(registeredClient.getClientId());
1156+
1157+
String state = extractParameterFromRedirectUri(redirectedUrl, "state");
1158+
OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE);
1159+
assertThat(authorization).isNotNull();
1160+
}
1161+
10741162
private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) {
10751163
Map<String, Object> additionalParameters = new HashMap<>();
10761164
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
@@ -1125,8 +1213,8 @@ private static MultiValueMap<String, String> getTokenRequestParameters(Registere
11251213
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
11261214
String clientId = registeredClient.getClientId();
11271215
String clientSecret = registeredClient.getClientSecret();
1128-
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
1129-
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
1216+
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
1217+
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
11301218
String credentialsString = clientId + ":" + clientSecret;
11311219
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
11321220
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
@@ -1496,4 +1584,28 @@ SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) th
14961584

14971585
}
14981586

1587+
@EnableWebSecurity
1588+
@Configuration(proxyBeanMethods = false)
1589+
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage
1590+
extends AuthorizationServerConfiguration {
1591+
1592+
// @formatter:off
1593+
@Bean
1594+
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
1595+
http
1596+
.oauth2AuthorizationServer((authorizationServer) ->
1597+
authorizationServer
1598+
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
1599+
.authorizationEndpoint((authorizationEndpoint) ->
1600+
authorizationEndpoint.consentPage(consentPage))
1601+
)
1602+
.authorizeHttpRequests((authorize) ->
1603+
authorize.anyRequest().authenticated()
1604+
);
1605+
return http.build();
1606+
}
1607+
// @formatter:on
1608+
1609+
}
1610+
14991611
}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.util.Arrays;
2222
import java.util.Base64;
2323
import java.util.Collections;
24+
import java.util.HashMap;
2425
import java.util.HashSet;
26+
import java.util.Map;
2527
import java.util.Set;
2628
import java.util.function.Consumer;
2729
import java.util.function.Predicate;
@@ -283,8 +285,13 @@ public Authentication authenticate(Authentication authentication) throws Authent
283285
Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
284286
? currentAuthorizationConsent.getScopes() : null;
285287

288+
Map<String, Object> additionalParameters = new HashMap<>();
289+
if (pushedAuthorization != null) {
290+
additionalParameters.put(OAuth2ParameterNames.SCOPE, authorizationRequest.getScopes());
291+
}
292+
286293
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
287-
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
294+
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, additionalParameters);
288295
}
289296

290297
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationCodeRequestAuthentication,

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,20 @@ private void sendAuthorizationConsent(HttpServletRequest request, HttpServletRes
291291

292292
String clientId = authorizationConsentAuthentication.getClientId();
293293
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
294-
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
295294
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
296295
String state = authorizationConsentAuthentication.getState();
297296

297+
Set<String> requestedScopes;
298+
String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters()
299+
.get(OAuth2ParameterNames.REQUEST_URI);
300+
if (StringUtils.hasText(requestUri)) {
301+
requestedScopes = (Set<String>) authorizationConsentAuthentication.getAdditionalParameters()
302+
.get(OAuth2ParameterNames.SCOPE);
303+
}
304+
else {
305+
requestedScopes = authorizationCodeRequestAuthentication.getScopes();
306+
}
307+
298308
if (hasConsentUri()) {
299309
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))
300310
.queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes))

0 commit comments

Comments
 (0)