-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the bug
When a registeredClient
is configured with requireAuthorizationConsent
set to true
combined with a OAuth2 Pushed Authorization Request, the end user gets a consent form displayed but without any scopes. Also when configuring a custom .consentPage()
on the OAuth2AuthorizationEndpointConfigurer
the scope parameter remains empty (?scope=&client_id=...
.
To Reproduce
Initiate a OAuth2 Pushed Authorization Request with a registeredClient
with .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
or using
spring:
security:
oauth2:
authorizationserver:
client:
my-client:
require-authorization-consent: true
Expected behavior
- The default consent page should request the scopes to the end user as initially provided in the OAuth2 Pushed Authorization Request
- If a
consentPage()
is configured on theOAuth2AuthorizationEndpointConfigurer
it should fill thescope
parameter with the initially provided scopes in the OAuth2 Pushed Authorization Request
Sample
Add the following tests and configuration to the class org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationCodeGrantTests
.
This is a combination of the existing requestWhenPushedAuthorizationRequestThenReturnAccessTokenResponse
, requestWhenRequiresConsentThenDisplaysConsentPage
and requestWhenCustomConsentPageConfiguredThenRedirect
tests already present in the test class
@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))
.param(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
.param(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
.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"));
}
@Test
public void requestWhenWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() 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))
.param(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
.param(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
.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();
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage
extends AuthorizationServerConfiguration {
// @formatter:off
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
.authorizationEndpoint((authorizationEndpoint) ->
authorizationEndpoint.consentPage(consentPage))
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
}