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 08027df456..cab8545fdb 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. @@ -169,7 +169,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/DefaultJwtBearerTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java index 419b5f91d6..feae305eda 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java @@ -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; @@ -73,19 +72,12 @@ public final class DefaultJwtBearerTokenResponseClient Assert.notNull(jwtBearerGrantRequest, "jwtBearerGrantRequest cannot be null"); RequestEntity request = this.requestEntityConverter.convert(jwtBearerGrantRequest); 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(jwtBearerGrantRequest.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 99dc69b029..e9a8305c88 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-2021 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. @@ -295,7 +295,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -307,7 +307,7 @@ public class DefaultAuthorizationCodeTokenResponseClientTests { this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient .getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build())); - 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 c6e6492eb3..171ad86b3d 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-2021 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. @@ -304,7 +304,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -317,7 +317,7 @@ public class DefaultClientCredentialsTokenResponseClientTests { this.clientRegistration.build()); 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/DefaultJwtBearerTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java index 1077bc08d4..89bd0d42f1 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java @@ -102,7 +102,8 @@ public class DefaultJwtBearerTokenResponseClientTests { 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)); @@ -204,7 +205,7 @@ public class DefaultJwtBearerTokenResponseClientTests { } @Test - public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() { + public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n" @@ -217,7 +218,7 @@ public class DefaultJwtBearerTokenResponseClientTests { this.jwtAssertion); OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient .getTokenResponse(jwtBearerGrantRequest); - 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 195ab0b858..1e55f4a398 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-2021 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. @@ -102,7 +102,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)); @@ -136,7 +137,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)); @@ -268,6 +270,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.clientRegistration.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 beae7489b0..2c9c486339 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-2021 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. @@ -104,7 +104,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)); @@ -131,6 +132,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.clientRegistration + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_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 getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { // @formatter:off 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 b3be304a3f..dbe54b5db0 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 ca7d4ee9c3..f2ba0095ec 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();