Skip to content

SAML: Add RequestedAuthnContext to AuthnRequest in OpenSamlAuthenticationRequestFactory #8141

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

Closed
Primedo opened this issue Mar 18, 2020 · 24 comments
Assignees
Labels
in: saml2 An issue in SAML2 modules type: enhancement A general enhancement
Milestone

Comments

@Primedo
Copy link

Primedo commented Mar 18, 2020

Summary

Add RequestedAuthnContext with Comparison and AuthnContextClassRef to require a certain authentication from the IdP.

Actual Behavior

OpenSamlAuthenticationRequestFactory creates the AuthnRequest with an Saml2AuthenticationRequest, but isn't possible to modify the AuthnRequest.

Expected Behavior

Either transport the required information via Saml2AuthenticationRequestContext or allow the modification of the created AuthnRequest before it is serialized.

Version

5.3.0-RELEASE

Additional Information

I am willing to work on this issue, but I am uncertain, what the expected direction could be. Personally I would prefer something like an ObjectPostProcessor where I would also have access to the HttpServletRequest so I could adjust the AuthnRequest according to the current user.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 18, 2020
@jzheaux
Copy link
Contributor

jzheaux commented Mar 18, 2020

Thanks for the suggestion, @Primedo.

I think it makes sense to add support for custom attributes to Saml2AuthenticationRequestContext.

Since AuthenticationRequestFactory is abstracted away from the Servlet API, though, a post processor that includes HttpServletRequest in the contract likely wouldn't work.

What would you think of a Converter<Saml2AuthenticationRequestContext, AuthnRequest> setter on OpenSamlAuthenticationRequestFactory? The converter could use the custom context attributes to control how the AuthnRequest is configured.

@jzheaux jzheaux added in: saml2 An issue in SAML2 modules and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 18, 2020
@Primedo
Copy link
Author

Primedo commented Mar 20, 2020

Josh, thank you for your feedback.

This is what I came up with until now and I hope it isn't the complete opposite of what you suggested: master...Primedo:gh-8141

I removed Saml2AuthenticationRequest since it was deprecated and I couldn't create a Saml2AuthenticationRequestContext from it.

Since I didn't want to create a new AuthnRequest and wanted to reuse the OpenSamlImplementation to create Objects I created a different converter with again another Context Converter<OpenSamlAuthenticationRequestContext, AuthnRequest>

What do you think, is this going in the right direction and what should I change?

@jzheaux
Copy link
Contributor

jzheaux commented Mar 23, 2020

Thanks for taking some time to try this out, @Primedo.

Let's see if it's possible to only focus on customizing the AuthnRequest. For example, I don't think we should remove methods in this PR. Indeed, if they must be removed for this feature to be added, then we'd need to wait until 6.0 to do it.

Regarding custom attributes, let's follow the pattern set out in OAuth2AuthorizationContext. Note that there the method is called getAttributes and the builder has a method called attributes that takes a Consumer.

As for a composite OpenSamlAuthenticationRequestContext object, I'm hesitant to add a new type to address this enhancement. Can you elaborate on precisely what you are wanting to add to the AuthnRequest and what additional context you need to be able to configure it correctly?

@Primedo
Copy link
Author

Primedo commented Mar 24, 2020

@jzheaux, what is your take on this change?
master...Primedo:gh-8141

I skipped the suggested Converter for this change.

@jzheaux
Copy link
Contributor

jzheaux commented Mar 25, 2020

I think I need to have a clearer idea on what inputs are determining what outputs. So far, I understand that you'd prefer to take something from the HttpServletRequest, but I'm not clear on what and on how that manifests itself in the resulting AuthnRequest.

Can you elaborate on precisely what you are wanting to add to the AuthnRequest and what inputs determine what gets added?

@Primedo
Copy link
Author

Primedo commented Mar 25, 2020

I want the current user (anonymous or authenticated) to authenticate with an exact or better authentication method based on the needed authentication level for the desired action (e. g. second factor to change password, identity card to change address, username-password or better for initial authentication). And RequestedAuthnContext could be used to transport the desired authentication level.

https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf

Does this explain the requirement?

@jzheaux
Copy link
Contributor

jzheaux commented Mar 25, 2020

Yes, that helps, @Primedo.

When it's an authenticated user, are the details you need tied to the current Authentication? For example, could you do Authentication current = SecurityContextHolder.getContext().getAuthentication(); and find your inputs from current?

@Primedo
Copy link
Author

Primedo commented Mar 25, 2020

and find your inputs from current

Yes, that would work.

And for anonymous users it would be helpful to have some context (cookie, URL, session or something else), so that the desired RequestedAuthnContext is known before creating the SAML Request.

@jzheaux
Copy link
Contributor

jzheaux commented Mar 25, 2020

And for anonymous users it would be helpful to have some context (cookie, URL, session or something else)

Do you have a concrete requirement here? Otherwise, maybe we wait on that aspect for now.

Yes, that would work.

One thing you should be able to do then, which would require no enhancement at all, is for you to extend OpenSAML's AuthnRequestMarshaller, registering it with XMLObjectProviderRegistrySupport.registerObjectProvider. There, you'd have full control over post-processing the AuthnRequest.

@Primedo
Copy link
Author

Primedo commented Mar 25, 2020

Do you have a concrete requirement here? Otherwise, maybe we wait on that aspect for now.

An anonymous user is on the public site and wants to change his address. Since address change does require an authentication by id card or better this information should be transported to the IdP, otherwise the anonymous user would maybe have to authenticate twice with different methods.

which would require no enhancement at all,

Thanks, is this your preferred solution for this issue?

@jzheaux
Copy link
Contributor

jzheaux commented Mar 25, 2020

Yes, @Primedo, if you can work with OpenSAML directly for your customization of the AuthnRequest, that would be ideal.

Of course, feel free to reopen this ticket if you run into trouble and want to take another look at changing OpenSamlAuthenticationRequestFactory.

@jzheaux jzheaux closed this as completed Mar 25, 2020
@jzheaux jzheaux added the status: declined A suggestion or change that we don't feel we should currently apply label Mar 25, 2020
@fpagliar
Copy link

This request was actually totally on point about limited support in the creation of AuthNRequests.
See this issue

@jzheaux jzheaux reopened this Apr 3, 2020
@jzheaux jzheaux removed the status: declined A suggestion or change that we don't feel we should currently apply label Apr 3, 2020
@jzheaux
Copy link
Contributor

jzheaux commented Apr 3, 2020

To reiterate, I think it would be helpful to answer the following question:

What specifically do you need to get from HttpServletRequest and how does it affect the AuthnRequest?

@fpagliar
Copy link

fpagliar commented Apr 4, 2020

I understand that SAML is currently on MVP state for Spring Security 5.2, but spring-security-saml is no longer supported, so 5.2 is the only valid option at the moment.
There will be an increasing flow of teams collaborating to achieve feature parity and common migration patterns.

Back to the issue, the goal as a developer using the framework is to be able to customize request creation without re-writing Saml2WebSsoAuthenticationRequestFilter and Saml2AuthenticationRequestFactory.
Instead I want to be able to create and enhanced Saml2AuthenticationRequestContext with the information needed, and use composition on the factory to customize the AuthnRequest but then delegate the rest of the logic (XML creation, signature) on the default implementation.

What specifically do you need to get from HttpServletRequest and how does it affect the AuthnRequest?

Sample scenarios for this case would be:

  • Override ForceAuthN based on a querystring attribute. If the SAML response is rejected due to MaxAuthenticationAge, a simple solution and great user experience is to trigger a new SAML flow with ForceAuthN = True to fetch a fresh sign in from the IdP. So on the SAMLResponse error handling, we can redirect back to the entry point with ForceAuthN=true.

  • Different connections. We can look at the incoming http request, for example look at private vs public IP addresses and require the external connections to do a 2FA on the IdP by adding AuthnContextClassRef tags.

  • Extended Attributes. SAML supports sending custom attributes to the IdP within the request.
    Those would be passed on the original http request and could contain information on what type of resource the user tried to access that triggered the redirect and SP initiated flow, for example.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 4, 2020

@fpagliar good points, @Primedo as well, and I'm happy to have both of you as collaborators to help discover the right contract here.

I agree that it will likely be helpful to modify the AuthnRequest based on request material. Saml2AuthenticationRequestFactory is abstracted away from the request, though, so I imagine that we'll need three things:

  • A way to construct a custom Saml2AuthenticationRequestContext
  • A way to provide custom attributes to Saml2AuthenticationRequestContext
  • A way to post-process the AuthnRequest

@Primedo and I made some initial progress on these points. Building off of that progress, let me know what you both think of the following contract for the ForceAuthN use case (as it seems pretty simple). Most of this support doesn't exist yet, so it's just for illustration on what might be possible:

@Bean 
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
    Saml2AuthenticationRequestContextResolver delegate =
            new DefaultSaml2AuthenticationRequestContextResolver();
    return request -> {
        Saml2AuthenticationRequestContext.Builder builder = delegate.resolve(request);
        return builder.attribute("is-force-authn", request.getParameter("force"));
    }
}

@Bean 
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
    OpenSamlAuthenticationRequestFactory factory =
            new OpenSamlAuthenticationRequestFactory();
    factory.setAuthnRequestPostProcessor((context, authnRequest) -> {
        if (StringUtils.hasText(context.getAttribute("is-force-authn"))) {
            authnRequest.setForceAuthn(true);
        }
    });
    return factory;
}

The idea here is that the filter would call the Saml2AuthenticationRequestContextResolver, get a Saml2AuthenticationRequestContext.Builder, build it, and pass it to the Saml2AuthenticationRequestFactory. The factory would use the post-processor (a BiConsumer, I'm thinking), to post-process the initial AuthnRequest that the factory constructed.

The SAML 2.0 specification is obviously large and I'd prefer to manage Saml2AuthenticationRequestContext's growth carefully. It's tricky to know where to draw the line on what properties to introduce and what to leave out. So, what I like about this approach is it adds a small amount of code on the Spring Security side while allowing a lot of flexibility from applications to customize the AuthnRequest, e.g. we don't need to add a setForceAuthN method, a setAuthnContextClassRef method, etc.

I also like that this leaves all OpenSAML references inside of OpenSamlAuthenticationRequestFactory. It favors composition, which is nice.

Do you believe this would address this use case as well as the others? Where do you see areas for improvement?

@Primedo
Copy link
Author

Primedo commented Apr 6, 2020

Do you believe this would address this use case as well as the others?

Yes, and I agree upon managing Saml2AuthenticationRequestContext's growth carefully.

Where do you see areas for improvement?

Just two cents, where I also find the other solution fine:

  • I had a similar method in my branch as your suggested post-processor and also returned the AuthnRequest, but I was not sure, if this hides that the object is also modified.

  • Rethink Saml2AuthenticationRequestFactory's usage and goal and maybe also provide the request within the post-processor so someone could skip copying attributes from the Resolver to the Factory.

@fpagliar
Copy link

fpagliar commented Apr 6, 2020

I think the overall strategy looks good.
I agree that we want to keep the context's growth under control.
Turning the context into an interface or allowing it to be extended and passing on a generic T extends Saml2AuthenticationRequestContext to the postProcessor would enable the devs to have a more rich/complex typesafe passage of arguments.

Although I think the current proposal with a string map of custom attributes is probably good enough for my current usecase.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 7, 2020

I was not sure, if this hides that the object is also modified.

I think that the method name could take care of this, @Primedo. I'm thinking setAuthnRequestPostProcessor, but do you see a different method name that clarifies that the AuthnRequest parameter is the one being modified?

(Also note that I modified my sample above as I found a bug in it -- see if that also resolves your concern.)

and also returned the AuthnRequest

That's a good point, we'd originally talked about it being a Converter.

It depends on whether the application is constructing an AuthnRequest from scratch or post-processing one that Spring Security constructs. If generated from scratch, I think a Converter<Saml2AuthenticationRequestContext, AuthnRequest> makes sense. Otherwise, BiConsumer<Saml2AuthenticationRequestContext, AuthnRequest> makes sense.

The reason I proposed a BiConsumer was because of:

Since I didn't want to create a new AuthnRequest and wanted to reuse the OpenSamlImplementation to create Objects

combined with the fact that if an application wants to customize the construction of an AuthnRequest, then they can do that by registering a custom AuthnRequestBuilder.

Rethink Saml2AuthenticationRequestFactory's usage and goal and maybe also provide the request within the post-processor so someone could skip copying attributes from the Resolver to the Factory.

I agree that this would be a nice thing to consider down the road, perhaps in a new component. As it is, the factory is at the service level so we don't want to have the HttpServletRequest as part of the contract.

I suppose an application could add the request as a custom attribute if they aren't concerned about leaking the request into the service layer. I don't think we'd want OpenSamlAuthenticationRequestFactory to treat HttpServletRequest as a first-class citizen, though.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 7, 2020

... allowing [the context] to be extended

@fpagliar, I think this is a polish we could possibly do later on - I'd like to wait a bit before making Saml2AuthenticationRequestContext's constructor non-private, which I believe is a requirement to making it extensible.

That said, if you've got a concrete use case that's a great deal simpler with a custom context, then it's something worth considering.

@jzheaux
Copy link
Contributor

jzheaux commented Apr 8, 2020

@fpagliar Thinking a bit more about your recommendation that Saml2AuthenticationRequestContext be opened up, I think this would be a good way to begin. Adding custom data, whether via a Map of attributes or via extension, is really at the center of all of these use cases: #8356

@jzheaux
Copy link
Contributor

jzheaux commented Apr 8, 2020

Regarding the other two items, I've created a ticket for adding the resolver next: #8360

The reason that this is next in priority is that it will address additional cases, like for custom implementations of Saml2AuthenticationRequestFactory.

I'm still analyzing the implications of exposing an AuthnRequest post-processor in OpenSamlAuthenticationRequestFactory. I think OpenSamlAuthenticationRequestFactory may expose other components over time, and I'd like to see how they interact before moving forward with a post-processor. Also, I'd like to see in what way post-processing OpenSAML components might be helpful in OpenSamlAuthenticationProvider. In the meantime, please consider implementing Saml2AuthenticationRequestFactory to customize the AuthnRequest instance.

@fpagliar
Copy link

fpagliar commented Apr 9, 2020

This is definitely a step in the right direction, but I definitely feel like we need the post processor.
For the ForceAuthN case for example, how would it be implemented without it?
It is necessary to have that information stored in the context and that part is now covered.
But the only way I see of using it, is copying the whole OpenSamlAuthenticationRequestFactory and replacing just the one line that does auth.setForceAuthn(Boolean.FALSE);


@amergey
Copy link
Contributor

amergey commented Nov 30, 2020

It seems the ability to customize AuthnRequest which was added there, has been removed (or at least make more difficult) in 5.4.1 with af5c55c#diff-fc7cb5ee4992dc89c57d8930e6fcec7b4784e7f760ffe2480dce619f812e121e

For example, before it was quite easy to customize set ForceAuthn

OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
					new OpenSamlAuthenticationRequestFactory();
			authenticationRequestFactory.setAuthnRequestConsumerResolver(
					context -> authnRequest -> authnRequest.setForceAuthn(true));
			return authenticationRequestFactory;

In latest version it is necessary to write a full AuthnRequest builder by copying code from private methods just to override few things that needed customization.

@jzheaux
Copy link
Contributor

jzheaux commented Nov 30, 2020

has been removed

@amergey, to clarify, this feature was added in 5.4.0 and did not change in 5.4.1. The API has remained the same since it GA'd in September.

make more difficult

Please see if #9209 simplifies what you are trying to do.

In the meantime, several of the attributes that Spring Security sets are optional, so a copy/paste may bring in more than you need to maintain. For example, you can create a minimal AuthnRequest with:

OpenSamlAuthenticationRequestFactory factory  = new OpenSamlAuthenticationRequestFactory();
factory.setAuthenticationRequestContextConverter((context) -> {
    AuthnRequest request = authnRequestBuilder.buildObject();
    request.setID("A" + UUID.randomUUID());
    request.setIssueInstant(new DateTime());
    request.setForceAuthn(true);
    Issuer issuer = issuerBuilder.buildObject();
    issuer.setValue(context.getIssuer());
    request.setIssuer(issuer);
    return request;
});

Or, you might find it easier to register a custom AuthnRequestMarshaller with OpenSAML that post-processes the AuthnRequest.

Note the reason that the API was released as a Converter<Saml2AuthenticationRequestContext, AuthnRequest> instead of a Function<Saml2AuthenticationRequestContext, Consumer<AuthnRequest>> was to address more use cases while simplifying the contract. For example, with this contract, an application can create a completely custom AuthnRequest.

It also doesn't prevent the API in the future from adding a static method like createDefaultAuthenticationRequestContextConverter that a custom Converter could call, for example:

OpenSamlAuthenticationRequestFactory factory  = new OpenSamlAuthenticationRequestFactory();
factory.setAuthenticationRequestContextConverter(
    createDefaultAuthenticationRequestContextConverter()
        .andThen((authnRequest) -> authnRequest.setForceAuthn(true))
);

You can read more about both of these points in this commit message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: saml2 An issue in SAML2 modules type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants