Skip to content

Commit 7500458

Browse files
author
Steve Riesenberg
committed
Fix scope mapping
Issue gh-12101
1 parent 5dbfa57 commit 7500458

13 files changed

+147
-117
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
2727
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
2828
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
2929
import org.springframework.util.Assert;
30-
import org.springframework.util.CollectionUtils;
3130
import org.springframework.web.client.ResponseErrorHandler;
3231
import org.springframework.web.client.RestClientException;
3332
import org.springframework.web.client.RestOperations;
@@ -78,20 +77,12 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRe
7877
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
7978
throw new OAuth2AuthorizationException(oauth2Error, ex);
8079
}
81-
82-
OAuth2AccessTokenResponse tokenResponse = response.getBody();
83-
84-
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
85-
// As per spec, in Section 5.1 Successful Access Token Response
86-
// https://tools.ietf.org/html/rfc6749#section-5.1
87-
// If AccessTokenResponse.scope is empty, then default to the scope
88-
// originally requested by the client in the Token Request
89-
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
90-
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
91-
.build();
92-
}
93-
94-
return tokenResponse;
80+
// As per spec, in Section 5.1 Successful Access Token Response
81+
// https://tools.ietf.org/html/rfc6749#section-5.1
82+
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
83+
// granted.
84+
// However, we use the explicit scopes returned in the response (if any).
85+
return response.getBody();
9586
}
9687

9788
/**

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
2727
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
2828
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
2929
import org.springframework.util.Assert;
30-
import org.springframework.util.CollectionUtils;
3130
import org.springframework.web.client.ResponseErrorHandler;
3231
import org.springframework.web.client.RestClientException;
3332
import org.springframework.web.client.RestOperations;
@@ -78,20 +77,12 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRe
7877
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
7978
throw new OAuth2AuthorizationException(oauth2Error, ex);
8079
}
81-
82-
OAuth2AccessTokenResponse tokenResponse = response.getBody();
83-
84-
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
85-
// As per spec, in Section 5.1 Successful Access Token Response
86-
// https://tools.ietf.org/html/rfc6749#section-5.1
87-
// If AccessTokenResponse.scope is empty, then default to the scope
88-
// originally requested by the client in the Token Request
89-
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
90-
.scopes(clientCredentialsGrantRequest.getClientRegistration().getScopes())
91-
.build();
92-
}
93-
94-
return tokenResponse;
80+
// As per spec, in Section 5.1 Successful Access Token Response
81+
// https://tools.ietf.org/html/rfc6749#section-5.1
82+
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
83+
// granted.
84+
// However, we use the explicit scopes returned in the response (if any).
85+
return response.getBody();
9586
}
9687

9788
/**

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClient.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
2727
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
2828
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
2929
import org.springframework.util.Assert;
30-
import org.springframework.util.CollectionUtils;
3130
import org.springframework.web.client.ResponseErrorHandler;
3231
import org.springframework.web.client.RestClientException;
3332
import org.springframework.web.client.RestOperations;
@@ -78,20 +77,12 @@ public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest pas
7877
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
7978
throw new OAuth2AuthorizationException(oauth2Error, ex);
8079
}
81-
82-
OAuth2AccessTokenResponse tokenResponse = response.getBody();
83-
84-
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
85-
// As per spec, in Section 5.1 Successful Access Token Response
86-
// https://tools.ietf.org/html/rfc6749#section-5.1
87-
// If AccessTokenResponse.scope is empty, then default to the scope
88-
// originally requested by the client in the Token Request
89-
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
90-
.scopes(passwordGrantRequest.getClientRegistration().getScopes())
91-
.build();
92-
}
93-
94-
return tokenResponse;
80+
// As per spec, in Section 5.1 Successful Access Token Response
81+
// https://tools.ietf.org/html/rfc6749#section-5.1
82+
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
83+
// granted.
84+
// However, we use the explicit scopes returned in the response (if any).
85+
return response.getBody();
9586
}
9687

9788
/**

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -79,15 +79,7 @@ public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2AuthorizationCodeG
7979
})
8080
.body(body)
8181
.exchange()
82-
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
83-
.map(response -> {
84-
if (response.getAccessToken().getScopes().isEmpty()) {
85-
response = OAuth2AccessTokenResponse.withResponse(response)
86-
.scopes(authorizationExchange.getAuthorizationRequest().getScopes())
87-
.build();
88-
}
89-
return response;
90-
});
82+
.flatMap(response -> response.body(oauth2AccessTokenResponse()));
9183
});
9284
}
9385

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,15 +82,7 @@ public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2ClientCredentialsG
8282
null
8383
)));
8484
}
85-
return response.body(oauth2AccessTokenResponse()); })
86-
.map(response -> {
87-
if (response.getAccessToken().getScopes().isEmpty()) {
88-
response = OAuth2AccessTokenResponse.withResponse(response)
89-
.scopes(authorizationGrantRequest.getClientRegistration().getScopes())
90-
.build();
91-
}
92-
return response;
93-
});
85+
return response.body(oauth2AccessTokenResponse()); });
9486
});
9587
}
9688

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -79,18 +79,6 @@ public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2PasswordGrantReque
7979
.then(Mono.error(new OAuth2AuthorizationException(oauth2Error)));
8080
}
8181
return response.body(oauth2AccessTokenResponse());
82-
})
83-
.map(tokenResponse -> {
84-
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
85-
// As per spec, in Section 5.1 Successful Access Token Response
86-
// https://tools.ietf.org/html/rfc6749#section-5.1
87-
// If AccessTokenResponse.scope is empty, then default to the scope
88-
// originally requested by the client in the Token Request
89-
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
90-
.scopes(passwordGrantRequest.getClientRegistration().getScopes())
91-
.build();
92-
}
93-
return tokenResponse;
9482
});
9583
});
9684
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -218,7 +218,7 @@ public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasRe
218218
}
219219

220220
@Test
221-
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() {
221+
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() {
222222
String accessTokenSuccessResponse = "{\n" +
223223
" \"access_token\": \"access-token-1234\",\n" +
224224
" \"token_type\": \"bearer\",\n" +
@@ -230,7 +230,7 @@ public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessToke
230230
OAuth2AccessTokenResponse accessTokenResponse =
231231
this.tokenResponseClient.getTokenResponse(this.authorizationCodeGrantRequest());
232232

233-
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write");
233+
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty();
234234
}
235235

236236
@Test

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -222,7 +222,7 @@ public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasRe
222222
}
223223

224224
@Test
225-
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasDefaultScope() {
225+
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() {
226226
String accessTokenSuccessResponse = "{\n" +
227227
" \"access_token\": \"access-token-1234\",\n" +
228228
" \"token_type\": \"bearer\",\n" +
@@ -235,7 +235,7 @@ public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessToke
235235

236236
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
237237

238-
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write");
238+
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty();
239239
}
240240

241241
@Test

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,11 +86,14 @@ public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException()
8686

8787
@Test
8888
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception {
89-
String accessTokenSuccessResponse = "{\n" +
90-
" \"access_token\": \"access-token-1234\",\n" +
91-
" \"token_type\": \"bearer\",\n" +
92-
" \"expires_in\": \"3600\"\n" +
93-
"}\n";
89+
// @formatter:off
90+
String accessTokenSuccessResponse = "{\n"
91+
+ " \"access_token\": \"access-token-1234\",\n"
92+
+ " \"token_type\": \"bearer\",\n"
93+
+ " \"expires_in\": \"3600\",\n"
94+
+ " \"scope\": \"read write\"\n"
95+
+ "}\n";
96+
// @formatter:on
9497
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
9598

9699
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
@@ -123,11 +126,14 @@ public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() t
123126

124127
@Test
125128
public void getTokenResponseWhenClientAuthenticationPostThenFormParametersAreSent() throws Exception {
126-
String accessTokenSuccessResponse = "{\n" +
127-
" \"access_token\": \"access-token-1234\",\n" +
128-
" \"token_type\": \"bearer\",\n" +
129-
" \"expires_in\": \"3600\"\n" +
130-
"}\n";
129+
// @formatter:off
130+
String accessTokenSuccessResponse = "{\n"
131+
+ " \"access_token\": \"access-token-1234\",\n"
132+
+ " \"token_type\": \"bearer\",\n"
133+
+ " \"expires_in\": \"3600\",\n"
134+
+ " \"scope\": \"read\"\n"
135+
+ "}\n";
136+
// @formatter:on
131137
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
132138

133139
ClientRegistration clientRegistration = this.clientRegistrationBuilder
@@ -186,6 +192,22 @@ public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasRe
186192
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read");
187193
}
188194

195+
@Test
196+
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() {
197+
// @formatter:off
198+
String accessTokenSuccessResponse = "{\n"
199+
+ " \"access_token\": \"access-token-1234\",\n"
200+
+ " \"token_type\": \"bearer\",\n"
201+
+ " \"expires_in\": \"3600\"\n"
202+
+ "}\n";
203+
// @formatter:on
204+
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
205+
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
206+
this.clientRegistrationBuilder.build(), this.username, this.password);
207+
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
208+
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty();
209+
}
210+
189211
@Test
190212
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() {
191213
String accessTokenErrorResponse = "{\n" +

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -88,11 +88,14 @@ public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException()
8888

8989
@Test
9090
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception {
91-
String accessTokenSuccessResponse = "{\n" +
92-
" \"access_token\": \"access-token-1234\",\n" +
93-
" \"token_type\": \"bearer\",\n" +
94-
" \"expires_in\": \"3600\"\n" +
95-
"}\n";
91+
// @formatter:off
92+
String accessTokenSuccessResponse = "{\n"
93+
+ " \"access_token\": \"access-token-1234\",\n"
94+
+ " \"token_type\": \"bearer\",\n"
95+
+ " \"expires_in\": \"3600\",\n"
96+
+ " \"scope\": \"read write\"\n"
97+
+ "}\n";
98+
// @formatter:on
9699
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
97100

98101
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
@@ -121,6 +124,26 @@ public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() t
121124
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(this.refreshToken.getTokenValue());
122125
}
123126

127+
@Test
128+
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasOriginalScope() {
129+
// @formatter:off
130+
String accessTokenSuccessResponse = "{\n"
131+
+ " \"access_token\": \"access-token-1234\",\n"
132+
+ " \"token_type\": \"bearer\",\n"
133+
+ " \"expires_in\": \"3600\"\n"
134+
+ "}\n";
135+
// @formatter:on
136+
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
137+
ClientRegistration clientRegistration = this.clientRegistrationBuilder
138+
.clientAuthenticationMethod(ClientAuthenticationMethod.POST).build();
139+
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration,
140+
this.accessToken, this.refreshToken);
141+
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient
142+
.getTokenResponse(refreshTokenGrantRequest);
143+
assertThat(accessTokenResponse.getAccessToken().getScopes())
144+
.containsExactly(this.accessToken.getScopes().toArray(new String[0]));
145+
}
146+
124147
@Test
125148
public void getTokenResponseWhenClientAuthenticationPostThenFormParametersAreSent() throws Exception {
126149
String accessTokenSuccessResponse = "{\n" +

0 commit comments

Comments
 (0)