-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Provide more flexibility on when to display consent page #1552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ | |
import java.util.function.Consumer; | ||
|
||
import org.springframework.lang.Nullable; | ||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; | ||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; | ||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; | ||
import org.springframework.util.Assert; | ||
|
||
|
@@ -63,6 +65,27 @@ public RegisteredClient getRegisteredClient() { | |
return get(RegisteredClient.class); | ||
} | ||
|
||
/** | ||
* Returns the {@link OAuth2AuthorizationRequest oauth2 authorization request}. | ||
* | ||
* @return the {@link OAuth2AuthorizationRequest} | ||
*/ | ||
@Nullable | ||
public OAuth2AuthorizationRequest getOAuth2AuthorizationRequest() { | ||
return get(OAuth2AuthorizationRequest.class); | ||
} | ||
|
||
/** | ||
* Returns the {@link OAuth2AuthorizationConsent oauth2 authorization consent}. | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add |
||
* @return the {@link OAuth2AuthorizationConsent} | ||
*/ | ||
@Nullable | ||
public OAuth2AuthorizationConsent getOAuth2AuthorizationConsent() { | ||
return get(OAuth2AuthorizationConsent.class); | ||
} | ||
|
||
|
||
/** | ||
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. | ||
* | ||
|
@@ -92,6 +115,28 @@ public Builder registeredClient(RegisteredClient registeredClient) { | |
return put(RegisteredClient.class, registeredClient); | ||
} | ||
|
||
/** | ||
* Sets the {@link OAuth2AuthorizationRequest oauth2 authorization request}. | ||
* | ||
* @param authorizationRequest the {@link OAuth2AuthorizationRequest} | ||
* @return the {@link Builder} for further configuration | ||
* @since 1.3.0 | ||
*/ | ||
public Builder authorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { | ||
return put(OAuth2AuthorizationRequest.class, authorizationRequest); | ||
} | ||
|
||
/** | ||
* Sets the {@link OAuth2AuthorizationConsent oauth2 authorization consent}. | ||
* | ||
* @param authorizationConsent the {@link OAuth2AuthorizationConsent} | ||
* @return the {@link Builder} for further configuration | ||
* @since 1.3.0 | ||
*/ | ||
public Builder authorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { | ||
return put(OAuth2AuthorizationConsent.class, authorizationConsent); | ||
} | ||
|
||
/** | ||
* Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationContext}. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
import java.util.Base64; | ||
import java.util.Set; | ||
import java.util.function.Consumer; | ||
import java.util.function.Predicate; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
|
@@ -80,6 +81,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen | |
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator(); | ||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator = | ||
new OAuth2AuthorizationCodeRequestAuthenticationValidator(); | ||
private Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> requiresAuthorizationConsent; | ||
|
||
/** | ||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using the provided parameters. | ||
|
@@ -96,6 +98,7 @@ public OAuth2AuthorizationCodeRequestAuthenticationProvider(RegisteredClientRepo | |
this.registeredClientRepository = registeredClientRepository; | ||
this.authorizationService = authorizationService; | ||
this.authorizationConsentService = authorizationConsentService; | ||
this.requiresAuthorizationConsent = this::requireAuthorizationConsent; | ||
} | ||
|
||
@Override | ||
|
@@ -171,7 +174,19 @@ public Authentication authenticate(Authentication authentication) throws Authent | |
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById( | ||
registeredClient.getId(), principal.getName()); | ||
|
||
if (requireAuthorizationConsent(registeredClient, authorizationRequest, currentAuthorizationConsent)) { | ||
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = | ||
OAuth2AuthorizationCodeRequestAuthenticationContext.with(authorizationCodeRequestAuthentication) | ||
.registeredClient(registeredClient) | ||
.authorizationRequest(authorizationRequest); | ||
|
||
if (currentAuthorizationConsent != null) { | ||
authenticationContextBuilder.authorizationConsent(currentAuthorizationConsent); | ||
} | ||
|
||
OAuth2AuthorizationCodeRequestAuthenticationContext contextWithAuthorizationRequestAndAuthorizationConsent = | ||
authenticationContextBuilder.build(); | ||
|
||
if (requiresAuthorizationConsent.test(contextWithAuthorizationRequestAndAuthorizationConsent)) { | ||
String state = DEFAULT_STATE_GENERATOR.generateKey(); | ||
OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest) | ||
.attribute(OAuth2ParameterNames.STATE, state) | ||
|
@@ -264,7 +279,48 @@ public void setAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAu | |
this.authenticationValidator = authenticationValidator; | ||
} | ||
|
||
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal, | ||
/** | ||
* Sets the {@link Predicate} used to determine if authorization consent is required. | ||
* | ||
* <p> | ||
* The {@link OAuth2AuthorizationCodeRequestAuthenticationContext} gives the predicate access to the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}, | ||
* as well as, the following context attributes: | ||
* {@link OAuth2AuthorizationCodeRequestAuthenticationContext#getRegisteredClient()} containing {@link RegisteredClient} used to make the request. | ||
* {@link OAuth2AuthorizationCodeRequestAuthenticationContext#getOAuth2AuthorizationRequest()} containing {@link OAuth2AuthorizationRequest}. | ||
* {@link OAuth2AuthorizationCodeRequestAuthenticationContext#getOAuth2AuthorizationConsent()} containing {@link OAuth2AuthorizationConsent} granted in the request. | ||
* | ||
* @param requiresAuthorizationConsent the {@link Predicate} that determines if authorization consent is required. | ||
* @since 1.3.0 | ||
*/ | ||
public void setRequiresAuthorizationConsent(Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> requiresAuthorizationConsent) { | ||
Assert.notNull(requiresAuthorizationConsent, "requiresAuthorizationConsent cannot be null"); | ||
this.requiresAuthorizationConsent = requiresAuthorizationConsent; | ||
} | ||
|
||
private boolean requireAuthorizationConsent(OAuth2AuthorizationCodeRequestAuthenticationContext context) { | ||
RegisteredClient registeredClient = context.getRegisteredClient(); | ||
if (!registeredClient.getClientSettings().isRequireAuthorizationConsent()) { | ||
return false; | ||
} | ||
|
||
OAuth2AuthorizationRequest authorizationRequest = context.getOAuth2AuthorizationRequest(); | ||
// 'openid' scope does not require consent | ||
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID) && | ||
authorizationRequest.getScopes().size() == 1) { | ||
return false; | ||
} | ||
|
||
OAuth2AuthorizationConsent authorizationConsent = context.getOAuth2AuthorizationConsent(); | ||
if (authorizationConsent != null && | ||
authorizationConsent.getScopes().containsAll(authorizationRequest.getScopes())) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, | ||
Authentication principal, | ||
OAuth2AuthorizationRequest authorizationRequest) { | ||
return OAuth2Authorization.withRegisteredClient(registeredClient) | ||
.principalName(principal.getName()) | ||
|
@@ -295,26 +351,6 @@ private static OAuth2TokenContext createAuthorizationCodeTokenContext( | |
return tokenContextBuilder.build(); | ||
} | ||
|
||
private static boolean requireAuthorizationConsent(RegisteredClient registeredClient, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep this method and associated logic as-is but change the signature to: private boolean requireAuthorizationConsent(OAuth2AuthorizationCodeRequestAuthenticationContext context) We would also need to supply This method would then be the default for: private Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> requiresAuthorizationConsent; Applications could then override this via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. Would you like if Or do you want just to use regular There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have pushed a commit with a refactor according to your comment. I attempted to expose getter & builders methods in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. We should make the getter |
||
OAuth2AuthorizationRequest authorizationRequest, OAuth2AuthorizationConsent authorizationConsent) { | ||
|
||
if (!registeredClient.getClientSettings().isRequireAuthorizationConsent()) { | ||
return false; | ||
} | ||
// 'openid' scope does not require consent | ||
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID) && | ||
authorizationRequest.getScopes().size() == 1) { | ||
return false; | ||
} | ||
|
||
if (authorizationConsent != null && | ||
authorizationConsent.getScopes().containsAll(authorizationRequest.getScopes())) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static boolean isPrincipalAuthenticated(Authentication principal) { | ||
return principal != null && | ||
!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) && | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add
@since 1.3