diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index a97bd09c9a..ecc24d1f33 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -166,7 +166,7 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient defaultScopes(T grantRequest) { - return scopes(grantRequest); + return Collections.emptySet(); } /** diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java index db43cb47bf..a6aafb7a13 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; @@ -76,19 +75,12 @@ public final class DefaultAuthorizationCodeTokenResponseClient Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null"); RequestEntity request = this.requestEntityConverter.convert(authorizationCodeGrantRequest); ResponseEntity response = getResponse(request); - OAuth2AccessTokenResponse tokenResponse = response.getBody(); - if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { - // 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 Token Request - // @formatter:off - tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) - .scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes()) - .build(); - // @formatter:on - } - return tokenResponse; + // 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 we assume all requested scopes were + // granted. + // However, we use the explicit scopes returned in the response (if any). + return response.getBody(); } private ResponseEntity getResponse(RequestEntity request) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java index 168a85a9da..a1ee225a94 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; @@ -76,19 +75,12 @@ public final class DefaultClientCredentialsTokenResponseClient Assert.notNull(clientCredentialsGrantRequest, "clientCredentialsGrantRequest cannot be null"); RequestEntity request = this.requestEntityConverter.convert(clientCredentialsGrantRequest); ResponseEntity response = getResponse(request); - OAuth2AccessTokenResponse tokenResponse = response.getBody(); - if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { - // 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 Token Request - // @formatter:off - tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) - .scopes(clientCredentialsGrantRequest.getClientRegistration().getScopes()) - .build(); - // @formatter:on - } - return tokenResponse; + // 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 we assume all requested scopes were + // granted. + // However, we use the explicit scopes returned in the response (if any). + return response.getBody(); } private ResponseEntity getResponse(RequestEntity request) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java index 047e787885..3f1171a9db 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; @@ -75,16 +74,12 @@ public final class DefaultPasswordTokenResponseClient Assert.notNull(passwordGrantRequest, "passwordGrantRequest cannot be null"); RequestEntity request = this.requestEntityConverter.convert(passwordGrantRequest); ResponseEntity response = getResponse(request); - OAuth2AccessTokenResponse tokenResponse = response.getBody(); - if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { - // 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 Token Request - tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) - .scopes(passwordGrantRequest.getClientRegistration().getScopes()).build(); - } - return tokenResponse; + // 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 we assume all requested scopes were + // granted. + // However, we use the explicit scopes returned in the response (if any). + return response.getBody(); } private ResponseEntity getResponse(RequestEntity request) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java index 77926000d4..82d873619e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,11 +65,6 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient return Collections.emptySet(); } - @Override - Set defaultScopes(OAuth2AuthorizationCodeGrantRequest grantRequest) { - return grantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes(); - } - @Override BodyInserters.FormInserter populateTokenRequestBody(OAuth2AuthorizationCodeGrantRequest grantRequest, BodyInserters.FormInserter body) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java index 7ad39faf0c..f7df261b6b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java index bac8801ea6..f60e3567cc 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java index 7bd0f9ba04..89afbf06e0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -220,7 +220,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -232,7 +232,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient .getTokenResponse(this.authorizationCodeGrantRequest()); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); + assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); } @Test diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java index 6c7dee1f87..3e601e7774 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -222,7 +222,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -235,7 +235,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { this.clientRegistration); OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient .getTokenResponse(clientCredentialsGrantRequest); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); + assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); } @Test diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java index bbe593a627..f37bf40a28 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,7 +92,8 @@ public class DefaultPasswordTokenResponseClientTests { String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"read write\"\n" + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); @@ -126,7 +127,8 @@ public class DefaultPasswordTokenResponseClientTests { String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"read\"\n" + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); @@ -181,6 +183,22 @@ public class DefaultPasswordTokenResponseClientTests { assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); } + @Test + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\"\n" + + "}\n"; + // @formatter:on + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest( + this.clientRegistrationBuilder.build(), this.username, this.password); + OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest); + assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); + } + @Test public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { String accessTokenErrorResponse = "{\n" + " \"error\": \"unauthorized_client\"\n" + "}\n"; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java index 4909fffe49..530300e671 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -96,7 +96,8 @@ public class DefaultRefreshTokenTokenResponseClientTests { String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"read write\"\n" + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); @@ -123,6 +124,26 @@ public class DefaultRefreshTokenTokenResponseClientTests { assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(this.refreshToken.getTokenValue()); } + @Test + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasOriginalScope() { + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\"\n" + + "}\n"; + // @formatter:on + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + ClientRegistration clientRegistration = this.clientRegistrationBuilder + .clientAuthenticationMethod(ClientAuthenticationMethod.POST).build(); + OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, + this.accessToken, this.refreshToken); + OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient + .getTokenResponse(refreshTokenGrantRequest); + assertThat(accessTokenResponse.getAccessToken().getScopes()) + .containsExactly(this.accessToken.getScopes().toArray(new String[0])); + } + @Test public void getTokenResponseWhenClientAuthenticationPostThenFormParametersAreSent() throws Exception { String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java index f875b8e218..2f60d83a95 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -236,7 +236,7 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseUsingRequestedScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseWithNoScopes() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -248,8 +248,7 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClientTests { this.clientRegistration.scope("openid", "profile", "email", "address"); OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient .getTokenResponse(authorizationCodeGrantRequest()).block(); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile", "email", - "address"); + assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); } private OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest() { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java index 5021c34d60..fc00800551 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,6 +84,7 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests { RecordedRequest actualRequest = this.server.takeRequest(); String body = actualRequest.getUtf8Body(); assertThat(response.getAccessToken()).isNotNull(); + assertThat(response.getAccessToken().getScopes()).containsExactly("create"); assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)) .isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ="); assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser"); @@ -107,13 +108,14 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests { RecordedRequest actualRequest = this.server.takeRequest(); String body = actualRequest.getUtf8Body(); assertThat(response.getAccessToken()).isNotNull(); + assertThat(response.getAccessToken().getScopes()).containsExactly("create"); assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); assertThat(body).isEqualTo( "grant_type=client_credentials&client_id=client-id&client_secret=client-secret&scope=read%3Auser"); } @Test - public void getTokenResponseWhenNoScopeThenClientRegistrationScopesDefaulted() { + public void getTokenResponseWhenNoScopeThenReturnAccessTokenResponseWithNoScopes() { ClientRegistration registration = this.clientRegistration.build(); // @formatter:off enqueueJson("{\n" @@ -125,7 +127,7 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests { // @formatter:on OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest(registration); OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block(); - assertThat(response.getAccessToken().getScopes()).isEqualTo(registration.getScopes()); + assertThat(response.getAccessToken().getScopes()).isEmpty(); } @Test diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java index 3f00b3b43f..737fa3b267 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,7 +80,8 @@ public class WebClientReactivePasswordTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseWithNoScope() + throws Exception { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -109,6 +110,41 @@ public class WebClientReactivePasswordTokenResponseClientTests { assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); + assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); + assertThat(accessTokenResponse.getRefreshToken()).isNull(); + } + + @Test + public void getTokenResponseWhenSuccessResponseIncludesScopeThenReturnAccessTokenResponse() throws Exception { + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"read write\"\n" + + "}\n"; + // @formatter:on + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + Instant expiresAtBefore = Instant.now().plusSeconds(3600); + ClientRegistration clientRegistration = this.clientRegistrationBuilder.build(); + OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, + this.username, this.password); + OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest) + .block(); + Instant expiresAtAfter = Instant.now().plusSeconds(3600); + RecordedRequest recordedRequest = this.server.takeRequest(); + assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); + assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); + assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) + .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); + String formParameters = recordedRequest.getBody().readUtf8(); + assertThat(formParameters).contains("grant_type=password"); + assertThat(formParameters).contains("username=user1"); + assertThat(formParameters).contains("password=password"); + assertThat(formParameters).contains("scope=read+write"); + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); assertThat(accessTokenResponse.getAccessToken().getScopes()) .containsExactly(clientRegistration.getScopes().toArray(new String[0])); assertThat(accessTokenResponse.getRefreshToken()).isNull();