mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 00:32:14 +00:00
Use EntityId-lookup Components
Closes gh-12880
This commit is contained in:
parent
dbdf04f151
commit
3ad6c6ce06
@ -33,9 +33,8 @@ import org.springframework.security.saml2.provider.service.authentication.Abstra
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.OpenSamlAuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter;
|
||||
@ -292,11 +291,6 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
}
|
||||
|
||||
private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(B http) {
|
||||
RelyingPartyRegistrationRepository registrations = relyingPartyRegistrationRepository(http);
|
||||
return new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
}
|
||||
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(B http) {
|
||||
if (this.relyingPartyRegistrationRepository == null) {
|
||||
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
|
||||
@ -339,7 +333,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
return bean;
|
||||
}
|
||||
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
|
||||
relyingPartyRegistrationResolver(http));
|
||||
relyingPartyRegistrationRepository(http));
|
||||
openSaml4AuthenticationRequestResolver
|
||||
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
|
||||
return openSaml4AuthenticationRequestResolver;
|
||||
@ -352,10 +346,14 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
AuthenticationConverter authenticationConverterBean = getBeanOrNull(http,
|
||||
Saml2AuthenticationTokenConverter.class);
|
||||
if (authenticationConverterBean == null) {
|
||||
Assert.state(this.loginProcessingUrl.contains("{registrationId}"),
|
||||
"loginProcessingUrl must contain {registrationId} path variable");
|
||||
return new Saml2AuthenticationTokenConverter(
|
||||
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository));
|
||||
authenticationConverterBean = getBeanOrNull(http, OpenSamlAuthenticationTokenConverter.class);
|
||||
}
|
||||
if (authenticationConverterBean == null) {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(
|
||||
this.relyingPartyRegistrationRepository);
|
||||
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||
converter.setRequestMatcher(createLoginProcessingUrlMatcher(this.loginProcessingUrl));
|
||||
return converter;
|
||||
}
|
||||
return authenticationConverterBean;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -39,11 +39,10 @@ import org.springframework.security.saml2.provider.service.authentication.logout
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestValidatorParametersResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
|
||||
@ -216,17 +215,12 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
this.logoutHandlers = logout.getLogoutHandlers();
|
||||
this.logoutSuccessHandler = logout.getLogoutSuccessHandler();
|
||||
}
|
||||
RelyingPartyRegistrationResolver registrations = relyingPartyRegistrationResolver(http);
|
||||
RelyingPartyRegistrationRepository registrations = getRelyingPartyRegistrationRepository(http);
|
||||
http.addFilterBefore(createLogoutRequestProcessingFilter(registrations), CsrfFilter.class);
|
||||
http.addFilterBefore(createLogoutResponseProcessingFilter(registrations), CsrfFilter.class);
|
||||
http.addFilterBefore(createRelyingPartyLogoutFilter(registrations), LogoutFilter.class);
|
||||
}
|
||||
|
||||
private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(H http) {
|
||||
RelyingPartyRegistrationRepository registrations = getRelyingPartyRegistrationRepository(http);
|
||||
return new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
}
|
||||
|
||||
private RelyingPartyRegistrationRepository getRelyingPartyRegistrationRepository(H http) {
|
||||
if (this.relyingPartyRegistrationRepository != null) {
|
||||
return this.relyingPartyRegistrationRepository;
|
||||
@ -242,18 +236,21 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
}
|
||||
|
||||
private Saml2LogoutRequestFilter createLogoutRequestProcessingFilter(
|
||||
RelyingPartyRegistrationResolver registrations) {
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
|
||||
Saml2LogoutResponseResolver logoutResponseResolver = createSaml2LogoutResponseResolver(registrations);
|
||||
Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(registrations,
|
||||
RequestMatcher requestMatcher = createLogoutRequestMatcher();
|
||||
OpenSamlLogoutRequestValidatorParametersResolver parameters = new OpenSamlLogoutRequestValidatorParametersResolver(
|
||||
registrations);
|
||||
parameters.setRequestMatcher(requestMatcher);
|
||||
Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(parameters,
|
||||
this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers);
|
||||
filter.setLogoutRequestMatcher(createLogoutRequestMatcher());
|
||||
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
||||
return postProcess(filter);
|
||||
}
|
||||
|
||||
private Saml2LogoutResponseFilter createLogoutResponseProcessingFilter(
|
||||
RelyingPartyRegistrationResolver registrations) {
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
Saml2LogoutResponseFilter logoutResponseFilter = new Saml2LogoutResponseFilter(registrations,
|
||||
this.logoutResponseConfigurer.logoutResponseValidator(), this.logoutSuccessHandler);
|
||||
logoutResponseFilter.setLogoutRequestMatcher(createLogoutResponseMatcher());
|
||||
@ -261,7 +258,7 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return postProcess(logoutResponseFilter);
|
||||
}
|
||||
|
||||
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationResolver registrations) {
|
||||
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationRepository registrations) {
|
||||
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
|
||||
Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutRequestSuccessHandler = createSaml2LogoutRequestSuccessHandler(
|
||||
registrations);
|
||||
@ -290,15 +287,15 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
}
|
||||
|
||||
private Saml2RelyingPartyInitiatedLogoutSuccessHandler createSaml2LogoutRequestSuccessHandler(
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
Saml2LogoutRequestResolver logoutRequestResolver = this.logoutRequestConfigurer
|
||||
.logoutRequestResolver(relyingPartyRegistrationResolver);
|
||||
.logoutRequestResolver(registrations);
|
||||
return new Saml2RelyingPartyInitiatedLogoutSuccessHandler(logoutRequestResolver);
|
||||
}
|
||||
|
||||
private Saml2LogoutResponseResolver createSaml2LogoutResponseResolver(
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
return this.logoutResponseConfigurer.logoutResponseResolver(relyingPartyRegistrationResolver);
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
return this.logoutResponseConfigurer.logoutResponseResolver(registrations);
|
||||
}
|
||||
|
||||
private <C> C getBeanOrNull(Class<C> clazz) {
|
||||
@ -385,12 +382,11 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return this.logoutRequestValidator;
|
||||
}
|
||||
|
||||
private Saml2LogoutRequestResolver logoutRequestResolver(
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
private Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
if (this.logoutRequestResolver != null) {
|
||||
return this.logoutRequestResolver;
|
||||
}
|
||||
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
|
||||
return new OpenSaml4LogoutRequestResolver(registrations);
|
||||
}
|
||||
|
||||
}
|
||||
@ -454,10 +450,9 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return this.logoutResponseValidator;
|
||||
}
|
||||
|
||||
private Saml2LogoutResponseResolver logoutResponseResolver(
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
private Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
if (this.logoutResponseResolver == null) {
|
||||
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
|
||||
return new OpenSaml4LogoutResponseResolver(registrations);
|
||||
}
|
||||
return this.logoutResponseResolver;
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -91,7 +90,6 @@ import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
@ -308,12 +306,9 @@ public class Saml2LoginConfigurerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class)
|
||||
.autowire())
|
||||
.havingRootCause().isInstanceOf(IllegalStateException.class)
|
||||
.withMessage("loginProcessingUrl must contain {registrationId} path variable");
|
||||
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenAutowires()
|
||||
throws Exception {
|
||||
this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class).autowire();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -666,9 +666,9 @@ In a deployed application, that translates to:
|
||||
The prevailing URI patterns are as follows:
|
||||
|
||||
* `+/saml2/authenticate/{registrationId}+` - The endpoint that xref:servlet/saml2/login/authentication-requests.adoc[generates a `<saml2:AuthnRequest>`] based on the configurations for that `RelyingPartyRegistration` and sends it to the asserting party
|
||||
* `+/saml2/login/sso/{registrationId}+` - The endpoint that xref:servlet/saml2/login/authentication.adoc[authenticates an asserting party's `<saml2:Response>`] based on the configurations for that `RelyingPartyRegistration`
|
||||
* `+/saml2/logout/sso+` - The endpoint that xref:servlet/saml2/logout.adoc[processes `<saml2:LogoutRequest>` and `<saml2:LogoutResponse>` payloads]; the `RelyingPartyRegistration` is looked up from previously authenticated state
|
||||
* `+/saml2/saml2-service-provider/metadata/{registrationId}+` - The xref:servlet/saml2/metadata.adoc[relying party metadata] for that `RelyingPartyRegistration`
|
||||
* `+/login/saml2/sso/+` - The endpoint that xref:servlet/saml2/login/authentication.adoc[authenticates an asserting party's `<saml2:Response>`]; the `RelyingPartyRegistration` is looked up from previously authenticated state or the response's issuer if needed; also supports `+/login/saml2/sso/{registrationId}+`
|
||||
* `+/logout/saml2/sso+` - The endpoint that xref:servlet/saml2/logout.adoc[processes `<saml2:LogoutRequest>` and `<saml2:LogoutResponse>` payloads]; the `RelyingPartyRegistration` is looked up from previously authenticated state or the request's issuer if needed; also supports `+/logout/saml2/slo/{registrationId}+`
|
||||
* `+/saml2/metadata+` - The xref:servlet/saml2/metadata.adoc[relying party metadata] for the set of `RelyingPartyRegistration`s; also supports `+/saml2/metadata/{registrationId}+` or `+/saml2/service-provider-metadata/{registrationId}+` for a specific `RelyingPartyRegistration`
|
||||
|
||||
Since the `registrationId` is the primary identifier for a `RelyingPartyRegistration`, it is needed in the URL for unauthenticated scenarios.
|
||||
If you wish to remove the `registrationId` from the URL for any reason, you can <<servlet-saml2login-rpr-relyingpartyregistrationresolver,specify a `RelyingPartyRegistrationResolver`>> to tell Spring Security how to look up the `registrationId`.
|
||||
@ -849,122 +849,18 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
|
||||
|
||||
As seen so far, Spring Security resolves the `RelyingPartyRegistration` by looking for the registration id in the URI path.
|
||||
|
||||
There are a number of reasons you may want to customize that. Among them:
|
||||
Depending on the use case, a number of other strategies are also employed to derive one.
|
||||
For example:
|
||||
|
||||
* You may already <<relyingpartyregistrationresolver-single, know which `RelyingPartyRegistration` you need>>
|
||||
* You may be <<relyingpartyregistrationresolver-entityid, federating many asserting parties>>
|
||||
* For processing `<saml2:Response>`s, the `RelyingPartyRegistration` is looked up from the associated `<saml2:AuthRequest>` or from the `<saml2:Response#Issuer>` element
|
||||
* For processing `<saml2:LogoutRequest>`s, the `RelyingPartyRegistration` is looked up from the currently logged in user or from the `<saml2:LogoutRequest#Issuer>` element
|
||||
* For publishing metadata, the `RelyingPartyRegistration`s are looked up from any repository that also implements `Iterable<RelyingPartyRegistration>`
|
||||
|
||||
To customize the way that a `RelyingPartyRegistration` is resolved, you can configure a custom `RelyingPartyRegistrationResolver`.
|
||||
The default looks up the registration id from the URI's last path element and looks it up in your `RelyingPartyRegistrationRepository`.
|
||||
When this needs adjustment, you can turn to the specific components for each of these endpoints targeted at customizing this:
|
||||
|
||||
[NOTE]
|
||||
Remember that if you have any placeholders in your `RelyingPartyRegistration`, your resolver implementation should resolve them.
|
||||
|
||||
[[relyingpartyregistrationresolver-single]]
|
||||
==== Resolving to a Single Consistent `RelyingPartyRegistration`
|
||||
|
||||
You can provide a resolver that, for example, always returns the same `RelyingPartyRegistration`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
|
||||
|
||||
private final RelyingPartyRegistrationResolver delegate;
|
||||
|
||||
public SingleRelyingPartyRegistrationResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
this.delegate = new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {
|
||||
return this.delegate.resolve(request, "single");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationResolver) : RelyingPartyRegistrationResolver {
|
||||
override fun resolve(request: HttpServletRequest?, registrationId: String?): RelyingPartyRegistration? {
|
||||
return this.delegate.resolve(request, "single")
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
You might next take a look at how to use this resolver to customize xref:servlet/saml2/metadata.adoc#servlet-saml2login-metadata[`<saml2:SPSSODescriptor>` metadata production].
|
||||
|
||||
[[relyingpartyregistrationresolver-entityid]]
|
||||
==== Resolving Based on the `<saml2:Response#Issuer>`
|
||||
|
||||
When you have one relying party that can accept assertions from multiple asserting parties, you will have as many ``RelyingPartyRegistration``s as asserting parties, with the <<servlet-saml2login-rpr-duplicated, relying party information duplicated across each instance>>.
|
||||
|
||||
This carries the implication that the assertion consumer service endpoint will be different for each asserting party, which may not be desirable.
|
||||
|
||||
You can instead resolve the `registrationId` via the `Issuer`.
|
||||
A custom implementation of `RelyingPartyRegistrationResolver` that does this may look like:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SamlResponseIssuerRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
|
||||
private final InMemoryRelyingPartyRegistrationRepository registrations;
|
||||
|
||||
// ... constructor
|
||||
|
||||
@Override
|
||||
RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {
|
||||
if (registrationId != null) {
|
||||
return this.registrations.findByRegistrationId(registrationId);
|
||||
}
|
||||
String entityId = resolveEntityIdFromSamlResponse(request);
|
||||
for (RelyingPartyRegistration registration : this.registrations) {
|
||||
if (registration.getAssertingPartyDetails().getEntityId().equals(entityId)) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveEntityIdFromSamlResponse(HttpServletRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class SamlResponseIssuerRelyingPartyRegistrationResolver(val registrations: InMemoryRelyingPartyRegistrationRepository):
|
||||
RelyingPartyRegistrationResolver {
|
||||
@Override
|
||||
fun resolve(val request: HttpServletRequest, val registrationId: String): RelyingPartyRegistration {
|
||||
if (registrationId != null) {
|
||||
return this.registrations.findByRegistrationId(registrationId)
|
||||
}
|
||||
String entityId = resolveEntityIdFromSamlResponse(request)
|
||||
for (val registration : this.registrations) {
|
||||
if (registration.getAssertingPartyDetails().getEntityId().equals(entityId)) {
|
||||
return registration
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private resolveEntityIdFromSamlResponse(val request: HttpServletRequest): String {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
You might next take a look at how to use this resolver to customize xref:servlet/saml2/login/authentication.adoc#relyingpartyregistrationresolver-apply[`<saml2:Response>` authentication].
|
||||
* For SAML Responses, customize the `AuthenticationConverter`
|
||||
* For Logout Requests, customize the `Saml2LogoutRequestValidatorParametersResolver`
|
||||
* For Metadata, customize the `Saml2MetadataResponseResolver`
|
||||
|
||||
[[federating-saml2-login]]
|
||||
=== Federating Login
|
||||
@ -996,6 +892,7 @@ var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrati
|
||||
.stream().map { builder : RelyingPartyRegistration.Builder -> builder
|
||||
.registrationId(UUID.randomUUID().toString())
|
||||
.entityId("https://example.org/saml2/sp")
|
||||
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso")
|
||||
.build()
|
||||
}
|
||||
.collect(Collectors.toList()));
|
||||
@ -1006,15 +903,7 @@ Note that because the registration id is set to a random value, this will change
|
||||
There are several ways to address this; let's focus on a way that suits the specific use case of federation.
|
||||
|
||||
In many federation cases, all the asserting parties share service provider configuration.
|
||||
Given that Spring Security will by default include the `registrationId` in all many of its SAML 2.0 URIs, the next step is often to change these URIs to exclude the `registrationId`.
|
||||
|
||||
There are two main URIs you will want to change along those lines:
|
||||
|
||||
* <<relyingpartyregistrationresolver-entityid,Resolve by `<saml2:Response#Issuer>`>>
|
||||
* <<relyingpartyregistrationresolver-single,Resolve with a default `RelyingPartyRegistration`>>
|
||||
|
||||
[NOTE]
|
||||
Optionally, you may also want to change the Authentication Request location, but since this is a URI internal to the app and not published to asserting parties, the benefit is often minimal.
|
||||
Given that Spring Security will by default include the `registrationId` in the service provider metadata, another step is to change corresponding URIs to exclude the `registrationId`, which you can see has already been done in the above sample where the `entityId` and `assertionConsumerServiceLocation` are configured with a static endpoint.
|
||||
|
||||
You can see a completed example of this in {gh-samples-url}/servlet/spring-boot/java/saml2/saml-extension-federation[our `saml-extension-federation` sample].
|
||||
|
||||
|
@ -26,6 +26,7 @@ import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
@ -46,6 +47,20 @@ public final class OpenSaml4AuthenticationRequestResolver implements Saml2Authen
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Construct an {@link OpenSaml4AuthenticationRequestResolver}
|
||||
* @param registrations a repository for relying and asserting party configuration
|
||||
* @since 6.1
|
||||
*/
|
||||
public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver((request, id) -> {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return registrations.findByRegistrationId(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link OpenSaml4AuthenticationRequestResolver}
|
||||
*/
|
||||
|
@ -45,6 +45,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2P
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
@ -120,15 +122,19 @@ class OpenSamlAuthenticationRequestResolver {
|
||||
if (registration == null) {
|
||||
return null;
|
||||
}
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
String entityId = uriResolver.resolve(registration.getEntityId());
|
||||
String assertionConsumerServiceLocation = uriResolver
|
||||
.resolve(registration.getAssertionConsumerServiceLocation());
|
||||
AuthnRequest authnRequest = this.authnRequestBuilder.buildObject();
|
||||
authnRequest.setForceAuthn(Boolean.FALSE);
|
||||
authnRequest.setIsPassive(Boolean.FALSE);
|
||||
authnRequest.setProtocolBinding(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
Issuer iss = this.issuerBuilder.buildObject();
|
||||
iss.setValue(registration.getEntityId());
|
||||
iss.setValue(entityId);
|
||||
authnRequest.setIssuer(iss);
|
||||
authnRequest.setDestination(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
authnRequest.setAssertionConsumerServiceURL(registration.getAssertionConsumerServiceLocation());
|
||||
authnRequest.setAssertionConsumerServiceURL(assertionConsumerServiceLocation);
|
||||
authnRequestConsumer.accept(registration, authnRequest);
|
||||
if (authnRequest.getID() == null) {
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
|
@ -29,6 +29,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2R
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@ -52,13 +54,14 @@ public class OpenSamlAuthenticationRequestResolverTests {
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
.isEqualTo(uriResolver.resolve(registration.getAssertionConsumerServiceLocation()));
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(uriResolver.resolve(registration.getEntityId()));
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
@ -76,13 +79,14 @@ public class OpenSamlAuthenticationRequestResolverTests {
|
||||
.assertingPartyDetails((party) -> party.wantAuthnRequestsSigned(false)).build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
.isEqualTo(uriResolver.resolve(registration.getAssertionConsumerServiceLocation()));
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(uriResolver.resolve(registration.getEntityId()));
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
@ -114,13 +118,14 @@ public class OpenSamlAuthenticationRequestResolverTests {
|
||||
.build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
.isEqualTo(uriResolver.resolve(registration.getAssertionConsumerServiceLocation()));
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(uriResolver.resolve(registration.getEntityId()));
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
@ -137,13 +142,14 @@ public class OpenSamlAuthenticationRequestResolverTests {
|
||||
.assertingPartyDetails((party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST)).build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
.isEqualTo(uriResolver.resolve(registration.getAssertionConsumerServiceLocation()));
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(uriResolver.resolve(registration.getEntityId()));
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
|
Loading…
x
Reference in New Issue
Block a user