Skip to content

Commit b5d4736

Browse files
committed
openid scope does not require user consent
Closes gh-225
1 parent ece5f2b commit b5d4736

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.security.Principal;
1919
import java.util.Collections;
2020
import java.util.HashMap;
21+
import java.util.HashSet;
2122
import java.util.Map;
2223
import java.util.Set;
2324

@@ -145,7 +146,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
145146

146147
JoseHeader.Builder headersBuilder = JwtUtils.headers();
147148
JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(
148-
registeredClient, issuer, authorization.getPrincipalName(), authorizedScopes);
149+
registeredClient, issuer, authorization.getPrincipalName(),
150+
excludeOpenidIfNecessary(authorizedScopes));
149151

150152
// @formatter:off
151153
JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
@@ -167,7 +169,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
167169

168170
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
169171
jwtAccessToken.getTokenValue(), jwtAccessToken.getIssuedAt(),
170-
jwtAccessToken.getExpiresAt(), authorizedScopes);
172+
jwtAccessToken.getExpiresAt(), excludeOpenidIfNecessary(authorizedScopes));
171173

172174
OAuth2RefreshToken refreshToken = null;
173175
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
@@ -243,6 +245,15 @@ public Authentication authenticate(Authentication authentication) throws Authent
243245
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
244246
}
245247

248+
private static Set<String> excludeOpenidIfNecessary(Set<String> scopes) {
249+
if (!scopes.contains(OidcScopes.OPENID)) {
250+
return scopes;
251+
}
252+
scopes = new HashSet<>(scopes);
253+
scopes.remove(OidcScopes.OPENID);
254+
return scopes;
255+
}
256+
246257
@Override
247258
public boolean supports(Class<?> authentication) {
248259
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private void processAuthorizationRequest(HttpServletRequest request, HttpServlet
198198
.attribute(Principal.class.getName(), principal)
199199
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest);
200200

201-
if (registeredClient.getClientSettings().requireUserConsent()) {
201+
if (requireUserConsent(registeredClient, authorizationRequest)) {
202202
String state = this.stateGenerator.generateKey();
203203
OAuth2Authorization authorization = builder
204204
.attribute(OAuth2ParameterNames.STATE, state)
@@ -232,6 +232,15 @@ private void processAuthorizationRequest(HttpServletRequest request, HttpServlet
232232
}
233233
}
234234

235+
private static boolean requireUserConsent(RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
236+
// openid scope does not require consent
237+
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID) &&
238+
authorizationRequest.getScopes().size() == 1) {
239+
return false;
240+
}
241+
return registeredClient.getClientSettings().requireUserConsent();
242+
}
243+
235244
private void processUserConsent(HttpServletRequest request, HttpServletResponse response)
236245
throws IOException {
237246

@@ -264,11 +273,16 @@ private void processUserConsent(HttpServletRequest request, HttpServletResponse
264273
Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); // TODO Allow configuration for authorization code time-to-live
265274
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
266275
this.codeGenerator.generateKey(), issuedAt, expiresAt);
276+
Set<String> authorizedScopes = userConsentRequestContext.getScopes();
277+
if (userConsentRequestContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID)) {
278+
// openid scope is auto-approved as it does not require consent
279+
authorizedScopes.add(OidcScopes.OPENID);
280+
}
267281
OAuth2Authorization authorization = OAuth2Authorization.from(userConsentRequestContext.getAuthorization())
268282
.token(authorizationCode)
269283
.attributes(attrs -> {
270284
attrs.remove(OAuth2ParameterNames.STATE);
271-
attrs.put(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, userConsentRequestContext.getScopes());
285+
attrs.put(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
272286
})
273287
.build();
274288
this.authorizationService.save(authorization);
@@ -661,6 +675,8 @@ private static String generateConsentPage(HttpServletRequest request,
661675

662676
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
663677
OAuth2AuthorizationRequest.class.getName());
678+
Set<String> scopes = new HashSet<>(authorizationRequest.getScopes());
679+
scopes.remove(OidcScopes.OPENID); // openid scope does not require consent
664680
String state = authorization.getAttribute(
665681
OAuth2ParameterNames.STATE);
666682

@@ -695,7 +711,7 @@ private static String generateConsentPage(HttpServletRequest request,
695711
builder.append(" <input type=\"hidden\" name=\"client_id\" value=\"" + registeredClient.getClientId() + "\">");
696712
builder.append(" <input type=\"hidden\" name=\"state\" value=\"" + state + "\">");
697713

698-
for (String scope : authorizationRequest.getScopes()) {
714+
for (String scope : scopes) {
699715
builder.append(" <div class=\"form-group form-check py-1\">");
700716
builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"" + scope + "\" id=\"" + scope + "\" checked>");
701717
builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>");

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import java.time.Duration;
2020
import java.time.Instant;
2121
import java.time.temporal.ChronoUnit;
22+
import java.util.HashMap;
23+
import java.util.HashSet;
24+
import java.util.Map;
2225
import java.util.Set;
2326

2427
import org.junit.Before;
@@ -306,6 +309,9 @@ public void authenticateWhenValidCodeAndAuthenticationRequestThenReturnIdToken()
306309
assertThat(accessTokenContext.<OAuth2AuthorizationGrantAuthenticationToken>getAuthorizationGrant()).isEqualTo(authentication);
307310
assertThat(accessTokenContext.getHeaders()).isNotNull();
308311
assertThat(accessTokenContext.getClaims()).isNotNull();
312+
Map<String, Object> claims = new HashMap<>();
313+
accessTokenContext.getClaims().claims(claims::putAll);
314+
assertThat(claims.containsKey(OidcScopes.OPENID)).isFalse();
309315
// ID Token context
310316
JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1);
311317
assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
@@ -328,8 +334,9 @@ public void authenticateWhenValidCodeAndAuthenticationRequestThenReturnIdToken()
328334
assertThat(accessTokenAuthentication.getRegisteredClient().getId()).isEqualTo(updatedAuthorization.getRegisteredClientId());
329335
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
330336
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(updatedAuthorization.getAccessToken().getToken());
331-
assertThat(accessTokenAuthentication.getAccessToken().getScopes())
332-
.isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
337+
Set<String> accessTokenScopes = new HashSet<>(updatedAuthorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
338+
accessTokenScopes.remove(OidcScopes.OPENID);
339+
assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(accessTokenScopes);
333340
assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull();
334341
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getRefreshToken().getToken());
335342
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
5252
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
5353
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
54+
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
5455
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
5556
import org.springframework.util.StringUtils;
5657

@@ -445,6 +446,19 @@ public void doFilterWhenAuthorizationRequestPostThenAuthorizationResponse() thro
445446
doFilterWhenAuthorizationRequestThenAuthorizationResponse(registeredClient, request);
446447
}
447448

449+
@Test
450+
public void doFilterWhenAuthenticationRequestIncludesOnlyOpenidScopeThenDoesNotRequireConsent() throws Exception {
451+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
452+
.scopes(scopes -> {
453+
scopes.clear();
454+
scopes.add(OidcScopes.OPENID);
455+
})
456+
.clientSettings(ClientSettings::requireUserConsent)
457+
.build();
458+
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
459+
doFilterWhenAuthorizationRequestThenAuthorizationResponse(registeredClient, request);
460+
}
461+
448462
private void doFilterWhenAuthorizationRequestThenAuthorizationResponse(
449463
RegisteredClient registeredClient, MockHttpServletRequest request) throws Exception {
450464

@@ -772,11 +786,12 @@ public void doFilterWhenUserConsentRequestNotApprovedThenAccessDeniedError() thr
772786

773787
@Test
774788
public void doFilterWhenUserConsentRequestApprovedThenAuthorizationResponse() throws Exception {
775-
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
789+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
776790
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
777791
.thenReturn(registeredClient);
778792
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
779793
.principalName(this.authentication.getName())
794+
.attributes(attrs -> attrs.remove(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME))
780795
.build();
781796
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
782797
.thenReturn(authorization);
@@ -908,7 +923,9 @@ private static MockHttpServletRequest createUserConsentRequest(RegisteredClient
908923
request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
909924
request.addParameter(OAuth2ParameterNames.STATE, "state");
910925
for (String scope : registeredClient.getScopes()) {
911-
request.addParameter(OAuth2ParameterNames.SCOPE, scope);
926+
if (!OidcScopes.OPENID.equals(scope)) {
927+
request.addParameter(OAuth2ParameterNames.SCOPE, scope);
928+
}
912929
}
913930
request.addParameter("consent_action", "approve");
914931

0 commit comments

Comments
 (0)