-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Add ClientRegistration from OpenID Connect Discovery #5355
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
Add ClientRegistration from OpenID Connect Discovery #5355
Conversation
What about defining the OIDC discovery config in application.yaml (as described in #5155)? |
@sparty02 Thanks for your feedback! Making it simple to create the Bean definition will be a feature added to the Spring Boot AutoConfiguration support for Spring Security once this gets merged in. This aligns with the way creating ClientRegistration works today. Spring Security provides an API to create the ClientRegistration and Spring Boot provides a configuration convention to convert configuration properties into a ClientRegistrationRepository using the provided APIs. This arrangement is necessary because only with Spring Boot can we conditionally create the Beans and back out of the way. In the meantime you can always Externalize Configuration yourself. I hope this arrangement makes sense. Please let me know if you have additional questions or concerns. |
@rwinch thanks, sounds good! |
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.security.config.oauth2.client; |
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 to package ...oauth2.client.oidc
* Provider Configuration Response</a> to initialize the {@link ClientRegistration.Builder}. | ||
* | ||
* <p> | ||
* For example if the issuer provided is "https://example.com", then an "OpenID Provider Configuration Request" will |
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.
Use "" ;" entity for all
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.
" do not need to be encoded and this is difficult for readability. I'm going to leave this as is
public final class OidcConfigurationProvider { | ||
|
||
/** | ||
* Given the <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> creates a |
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.
"Creates a ClientRegistration.Builder using the provided Issuer by making an..."
* Provider Configuration Response</a> to initialize the {@link ClientRegistration.Builder}. | ||
* | ||
* <p> | ||
* For example if the issuer provided is "https://example.com", then an "OpenID Provider Configuration Request" will |
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.
"For example, if the..."
* @return a {@link ClientRegistration.Builder} that was initialized by the OpenID Provider Configuration. | ||
*/ | ||
public static ClientRegistration.Builder issuer(String issuer) { | ||
RestTemplate rest = new RestTemplate(); |
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 Assert.hasText(issuer)
...also should we validate it's a URI
?
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.
I think leaving this validation to the invocation of the RestTemplate is probably best.
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.
It's not going to provide a user-friendly message compared to "Issuer cannot be empty/null" if we apply the Assert
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.
I updated this to provide an exception with the issuer in it if we fail to get a successful result back since valid URLs might not produce valid result.
* @return a {@link ClientRegistration.Builder} that was initialized by the OpenID Provider Configuration. | ||
*/ | ||
public static ClientRegistration.Builder issuer(String issuer) { | ||
RestTemplate rest = new RestTemplate(); |
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.
Should we use WebClient
instead? Also, set as static member so we can re-use for each request?
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.
RestTemplate has fewer dependencies, so I think sticking with that makes sense. Especially since starting the container is blocking anyways.
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.
Is not need timeout settings?
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.
@kazuki43zoo I think at this point we are going to leave it encapsulated. We probably need a separate issue to handle things like timeout settings more holistically. Likely this will be done by allowing the user to provide a WebClient or RestOperations instance
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.
Related to @kazuki43zoo's question, would RestTemplate better serve as an injected dependency? I recognize this changes the contract, since the method won't be static anymore, but just throwing it out there.
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.
@jzheaux At some point. We need to decide more wholistically if this is going to be WebClient or RestOperations since this should be consistent throughout our APIs. For the moment, I think we should keep this private.
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.
@rwinch Thanks for your replay. I think keeping private is OK.
But I will afraid that it is possible become timeout of infinity in current implementation (new RestTemplate()
= default implementation of theSimpleClientHttpRequestFactory
).
Scope scope = metadata.getScopes(); | ||
if (scope == null) { | ||
// If null, default to "openid" which must be supported | ||
return Arrays.asList("openid"); |
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.
Use OidcScopes.OPENID
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.security.config.oauth2.client; |
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.
move to sub-package ...oauth2.client.oidc
List<com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod> metadataAuthMethods = metadata.getTokenEndpointAuthMethods(); | ||
// if null, the default includes client_secret_basic | ||
if (metadataAuthMethods != null && !metadataAuthMethods.contains(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) { | ||
throw new IllegalArgumentException("Only ClientAuthenticationMethod.BASIC is supported. The issuer \"" + issuer + "\" returned a configuration of " + metadataAuthMethods); |
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.
We also support ClientAuthenticationMethod.POST
String openidConfiguration = rest.getForObject(issuer + "/.well-known/openid-configuration", String.class); | ||
OIDCProviderMetadata metadata = parse(openidConfiguration); | ||
String name = URI.create(issuer).getHost(); | ||
List<com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod> metadataAuthMethods = metadata.getTokenEndpointAuthMethods(); |
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.
Another validation check can be applied, see 4.3. OpenID Provider Configuration Validation
The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information. This MUST also be identical to the iss Claim value in ID Tokens issued from this Issuer.
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.
Given we don't use the Issuer provided by the Configuration, what is the benefit of validating this? It is something that is up to the Provider (we are a client) to ensure that they are the same unless we (the Client) end up using this, I don't think we should validate it.
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.
What if there was a MITM scenario and the metadata response returned was from evil.com
instead. The client would than use evil.com
authorization and token endpoints
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.
@jgrandja I believe you are saying that if the returned payload has an issuer that is different from the one used to make the request, then the entire response is suspect, even if we aren't using the issuer value in the response. I agree with this assessment.
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.
If you aren't using HTTPS you are in trouble no matter what. A malicious user will respond with a valid issuer but an invalid endpoint of sorts. I went ahead and add the validation, but since we don't use the returned issuer, we are really validating the OpenID Configuration of the OP for the sake of validating vs any real gain.
I'd like to make a proposal for enhancing this feature. I see the need for using the OpenID Provider Metadata in other features as well. For example, I have an outstanding task for ID Token validation, see this comment Also, we may leverage some of the signature/encryption related meatadata in one or more What if we extracted some of the code in
This way What do you think? |
@jgrandja I am not sure if this makes sense. I was thinking that we will eventually add additional information to the ClientRegistration to account for things ike signature/encryption related metadata. From my perspective, all the information obtained from the endpoint is registration information. In any event, I'd suggest we hold off until we get that far. |
I'm not seeing that we would add this to Either way, we can hold off on this. I just figure it would be good to get this done now since you were in this already. |
Validate the issuer that was returned matches the issuer that was was requested. Issue: gh-5355
@sparty02 Now that this is merged into master, I created a Boot ticket for the Auto Configuration Support spring-projects/spring-boot#13210 |
With this user's can now create a ClientRegistration using something like this:
Fixes: gh-4413