|
@@ -31,6 +31,7 @@ import org.springframework.http.MediaType;
|
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
|
+import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
|
import org.springframework.test.web.servlet.MvcResult;
|
|
@@ -85,6 +86,17 @@ public class AuthorizationCodeGrantFlow {
|
|
|
* @return The state parameter for submitting consent for authorization
|
|
|
*/
|
|
|
public String authorize(RegisteredClient registeredClient) throws Exception {
|
|
|
+ return authorize(registeredClient, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Perform the authorization request and obtain a state parameter.
|
|
|
+ *
|
|
|
+ * @param registeredClient The registered client
|
|
|
+ * @param additionalParameters Additional parameters for the request
|
|
|
+ * @return The state parameter for submitting consent for authorization
|
|
|
+ */
|
|
|
+ public String authorize(RegisteredClient registeredClient, MultiValueMap<String, String> additionalParameters) throws Exception {
|
|
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
|
|
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
|
|
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
|
@@ -92,13 +104,18 @@ public class AuthorizationCodeGrantFlow {
|
|
|
parameters.set(OAuth2ParameterNames.SCOPE,
|
|
|
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
|
|
parameters.set(OAuth2ParameterNames.STATE, "state");
|
|
|
+ if (additionalParameters != null) {
|
|
|
+ parameters.addAll(additionalParameters);
|
|
|
+ }
|
|
|
|
|
|
+ // @formatter:off
|
|
|
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorize")
|
|
|
.params(parameters)
|
|
|
.with(user(this.username).roles("USER")))
|
|
|
.andExpect(status().isOk())
|
|
|
.andExpect(header().string("content-type", containsString(MediaType.TEXT_HTML_VALUE)))
|
|
|
.andReturn();
|
|
|
+ // @formatter:on
|
|
|
String responseHtml = mvcResult.getResponse().getContentAsString();
|
|
|
Matcher matcher = HIDDEN_STATE_INPUT_PATTERN.matcher(responseHtml);
|
|
|
|
|
@@ -120,14 +137,16 @@ public class AuthorizationCodeGrantFlow {
|
|
|
parameters.add(OAuth2ParameterNames.SCOPE, scope);
|
|
|
}
|
|
|
|
|
|
+ // @formatter:off
|
|
|
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/authorize")
|
|
|
.params(parameters)
|
|
|
.with(user(this.username).roles("USER")))
|
|
|
.andExpect(status().is3xxRedirection())
|
|
|
.andReturn();
|
|
|
+ // @formatter:on
|
|
|
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
|
|
assertThat(redirectedUrl).isNotNull();
|
|
|
- assertThat(redirectedUrl).matches("http://127.0.0.1:8080/\\S+\\?code=.{15,}&state=state");
|
|
|
+ assertThat(redirectedUrl).matches("\\S+\\?code=.{15,}&state=state");
|
|
|
|
|
|
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
|
|
|
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
|
|
@@ -143,29 +162,67 @@ public class AuthorizationCodeGrantFlow {
|
|
|
* @return The token response
|
|
|
*/
|
|
|
public Map<String, Object> getTokenResponse(RegisteredClient registeredClient, String authorizationCode) throws Exception {
|
|
|
+ return getTokenResponse(registeredClient, authorizationCode, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Exchange an authorization code for an access token.
|
|
|
+ *
|
|
|
+ * @param registeredClient The registered client
|
|
|
+ * @param authorizationCode The authorization code obtained from the authorization request
|
|
|
+ * @param additionalParameters Additional parameters for the request
|
|
|
+ * @return The token response
|
|
|
+ */
|
|
|
+ public Map<String, Object> getTokenResponse(RegisteredClient registeredClient, String authorizationCode, MultiValueMap<String, String> additionalParameters) throws Exception {
|
|
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
|
|
+ parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
|
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
|
|
parameters.set(OAuth2ParameterNames.CODE, authorizationCode);
|
|
|
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
|
|
+ if (additionalParameters != null) {
|
|
|
+ parameters.addAll(additionalParameters);
|
|
|
+ }
|
|
|
|
|
|
- HttpHeaders basicAuth = new HttpHeaders();
|
|
|
- basicAuth.setBasicAuth(registeredClient.getClientId(), "secret");
|
|
|
+ boolean publicClient = (registeredClient.getClientSecret() == null);
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ if (!publicClient) {
|
|
|
+ headers.setBasicAuth(registeredClient.getClientId(),
|
|
|
+ registeredClient.getClientSecret().replace("{noop}", ""));
|
|
|
+ }
|
|
|
|
|
|
+ // @formatter:off
|
|
|
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/token")
|
|
|
.params(parameters)
|
|
|
- .headers(basicAuth))
|
|
|
+ .headers(headers))
|
|
|
.andExpect(status().isOk())
|
|
|
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.APPLICATION_JSON_VALUE)))
|
|
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
|
|
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
|
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
|
|
- .andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
|
|
+ .andExpect(publicClient
|
|
|
+ ? jsonPath("$.refresh_token").doesNotExist()
|
|
|
+ : jsonPath("$.refresh_token").isNotEmpty()
|
|
|
+ )
|
|
|
.andExpect(jsonPath("$.scope").isNotEmpty())
|
|
|
.andExpect(jsonPath("$.id_token").isNotEmpty())
|
|
|
.andReturn();
|
|
|
+ // @formatter:on
|
|
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
|
String responseJson = mvcResult.getResponse().getContentAsString();
|
|
|
return objectMapper.readValue(responseJson, TOKEN_RESPONSE_TYPE_REFERENCE);
|
|
|
}
|
|
|
+
|
|
|
+ public static MultiValueMap<String, String> withCodeChallenge() {
|
|
|
+ MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
|
|
+ parameters.set(PkceParameterNames.CODE_CHALLENGE, "BqZZ8pTVLsiA3t3tDOys2flJTSH7LoL3Pp5ZqM_YOnE");
|
|
|
+ parameters.set(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
|
|
+ return parameters;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static MultiValueMap<String, String> withCodeVerifier() {
|
|
|
+ MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
|
|
+ parameters.set(PkceParameterNames.CODE_VERIFIER, "yZ6eB-lEB4BBhIzqoDPqXTTATC0Vkgov7qDF8ar2qT4");
|
|
|
+ return parameters;
|
|
|
+ }
|
|
|
}
|