Skip to content

Commit 367f074

Browse files
committed
Add RelyingPartyRegistrationResolver
Closes spring-projectsgh-9486
1 parent a128637 commit 367f074

File tree

14 files changed

+239
-44
lines changed

14 files changed

+239
-44
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter;
4848
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
4949
import org.springframework.security.saml2.provider.service.web.DefaultSaml2AuthenticationRequestContextResolver;
50+
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
5051
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
5152
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
5253
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -264,7 +265,8 @@ private void setAuthenticationRequestRepository(B http,
264265
private AuthenticationConverter getAuthenticationConverter(B http) {
265266
if (this.authenticationConverter == null) {
266267
return new Saml2AuthenticationTokenConverter(
267-
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository));
268+
(RelyingPartyRegistrationResolver) new DefaultRelyingPartyRegistrationResolver(
269+
this.relyingPartyRegistrationRepository));
268270
}
269271
return this.authenticationConverter;
270272
}
@@ -390,8 +392,9 @@ private Saml2AuthenticationRequestContextResolver getContextResolver(B http) {
390392
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
391393
Saml2AuthenticationRequestContextResolver.class);
392394
if (resolver == null) {
393-
return new DefaultSaml2AuthenticationRequestContextResolver(new DefaultRelyingPartyRegistrationResolver(
394-
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository));
395+
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
396+
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository);
397+
return new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
395398
}
396399
return resolver;
397400
}

docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ There are a number of reasons you may want to customize. Among them:
727727
* You may know that you will never be a multi-tenant application and so want to have a simpler URL scheme
728728
* You may identify tenants in a way other than by the URI path
729729

730-
To customize the way that a `RelyingPartyRegistration` is resolved, you can configure a custom `Converter<HttpServletRequest, RelyingPartyRegistration>`.
730+
To customize the way that a `RelyingPartyRegistration` is resolved, you can configure a custom `RelyingPartyRegistrationResolver`.
731731
The default looks up the registration id from the URI's last path element and looks it up in your `RelyingPartyRegistrationRepository`.
732732

733733
You can provide a simpler resolver that, for example, always returns the same relying party:
@@ -736,22 +736,27 @@ You can provide a simpler resolver that, for example, always returns the same re
736736
.Java
737737
[source,java,role="primary"]
738738
----
739-
public class SingleRelyingPartyRegistrationResolver
740-
implements Converter<HttpServletRequest, RelyingPartyRegistration> {
739+
public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
740+
741+
private final RelyingPartyRegistrationResolver delegate;
742+
743+
public SingleRelyingPartyRegistrationResolver(RelyingPartyRegistrationRepository registrations) {
744+
this.delegate = new DefaultRelyingPartyRegistrationResolver(registrations);
745+
}
741746
742747
@Override
743-
public RelyingPartyRegistration convert(HttpServletRequest request) {
744-
return this.relyingParty;
748+
public RelyingPartyRegistration resolve(HttpServletRequest request, String registrationId) {
749+
return this.delegate.resolve(request, "single");
745750
}
746751
}
747752
----
748753
749754
.Kotlin
750755
[source,kotlin,role="secondary"]
751756
----
752-
class SingleRelyingPartyRegistrationResolver : Converter<HttpServletRequest?, RelyingPartyRegistration?> {
753-
override fun convert(request: HttpServletRequest?): RelyingPartyRegistration? {
754-
return this.relyingParty
757+
class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationResolver) : RelyingPartyRegistrationResolver {
758+
override fun resolve(request: HttpServletRequest?, registrationId: String?): RelyingPartyRegistration? {
759+
return this.delegate.resolve(request, "single")
755760
}
756761
}
757762
----
@@ -1544,7 +1549,7 @@ You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the f
15441549
.Java
15451550
[source,java,role="primary"]
15461551
----
1547-
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
1552+
DefaultRelyingPartyRegistrationResolver relyingPartyRegistrationResolver =
15481553
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
15491554
Saml2MetadataFilter filter = new Saml2MetadataFilter(
15501555
relyingPartyRegistrationResolver,
@@ -1594,8 +1599,6 @@ filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}
15941599
----
15951600
====
15961601

1597-
ensuring that the `registrationId` hint is at the end of the path.
1598-
15991602
Or, if you have registered a custom relying party registration resolver in the constructor, then you can specify a path without a `registrationId` hint, like so:
16001603

16011604
====

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.security.saml2.provider.service.servlet.HttpSessionSaml2AuthenticationRequestRepository;
3030
import org.springframework.security.saml2.provider.service.servlet.Saml2AuthenticationRequestRepository;
3131
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
32+
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
3233
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
3334
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
3435
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -67,7 +68,9 @@ public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyin
6768
public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
6869
String filterProcessesUrl) {
6970
this(new Saml2AuthenticationTokenConverter(
70-
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)), filterProcessesUrl);
71+
(RelyingPartyRegistrationResolver) new DefaultRelyingPartyRegistrationResolver(
72+
relyingPartyRegistrationRepository)),
73+
filterProcessesUrl);
7174
}
7275

7376
/**

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.saml2.provider.service.servlet.Saml2AuthenticationRequestRepository;
4040
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
4141
import org.springframework.security.saml2.provider.service.web.DefaultSaml2AuthenticationRequestContextResolver;
42+
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
4243
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
4344
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4445
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -96,7 +97,9 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
9697
public Saml2WebSsoAuthenticationRequestFilter(
9798
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
9899
this(new DefaultSaml2AuthenticationRequestContextResolver(
99-
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)), requestFactory());
100+
(RelyingPartyRegistrationResolver) new DefaultRelyingPartyRegistrationResolver(
101+
relyingPartyRegistrationRepository)),
102+
requestFactory());
100103
}
101104

102105
private static Saml2AuthenticationRequestFactory requestFactory() {

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
import javax.servlet.http.HttpServletRequest;
2424

25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
2528
import org.springframework.core.convert.converter.Converter;
2629
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
2730
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
@@ -42,28 +45,51 @@
4245
* @since 5.4
4346
*/
4447
public final class DefaultRelyingPartyRegistrationResolver
45-
implements Converter<HttpServletRequest, RelyingPartyRegistration> {
48+
implements RelyingPartyRegistrationResolver, Converter<HttpServletRequest, RelyingPartyRegistration> {
49+
50+
private Log logger = LogFactory.getLog(getClass());
4651

4752
private static final char PATH_DELIMITER = '/';
4853

4954
private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
5055

51-
private final Converter<HttpServletRequest, String> registrationIdResolver = new RegistrationIdResolver();
56+
private final RequestMatcher registrationRequestMatcher = new AntPathRequestMatcher("/**/{registrationId}");
5257

5358
public DefaultRelyingPartyRegistrationResolver(
5459
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
5560
Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null");
5661
this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
5762
}
5863

64+
/**
65+
* {@inheritDoc}
66+
*/
5967
@Override
6068
public RelyingPartyRegistration convert(HttpServletRequest request) {
61-
String registrationId = this.registrationIdResolver.convert(request);
62-
if (registrationId == null) {
69+
return resolve(request, null);
70+
}
71+
72+
/**
73+
* {@inheritDoc}
74+
*/
75+
@Override
76+
public RelyingPartyRegistration resolve(HttpServletRequest request, String relyingPartyRegistrationId) {
77+
if (relyingPartyRegistrationId == null) {
78+
if (this.logger.isTraceEnabled()) {
79+
this.logger.trace("Attempting to resolve from " + this.registrationRequestMatcher
80+
+ " since registrationId is null");
81+
}
82+
relyingPartyRegistrationId = this.registrationRequestMatcher.matcher(request).getVariables()
83+
.get("registrationId");
84+
}
85+
if (relyingPartyRegistrationId == null) {
86+
if (this.logger.isTraceEnabled()) {
87+
this.logger.trace("Returning null registration since registrationId is null");
88+
}
6389
return null;
6490
}
6591
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository
66-
.findByRegistrationId(registrationId);
92+
.findByRegistrationId(relyingPartyRegistrationId);
6793
if (relyingPartyRegistration == null) {
6894
return null;
6995
}
@@ -111,16 +137,4 @@ private static String getApplicationUri(HttpServletRequest request) {
111137
return uriComponents.toUriString();
112138
}
113139

114-
private static class RegistrationIdResolver implements Converter<HttpServletRequest, String> {
115-
116-
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/**/{registrationId}");
117-
118-
@Override
119-
public String convert(HttpServletRequest request) {
120-
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
121-
return result.getVariables().get("registrationId");
122-
}
123-
124-
}
125-
126140
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultSaml2AuthenticationRequestContextResolver.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,24 @@ public final class DefaultSaml2AuthenticationRequestContextResolver
4242

4343
private final Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver;
4444

45+
/**
46+
* Construct a {@link DefaultSaml2AuthenticationRequestContextResolver}
47+
* @param relyingPartyRegistrationResolver
48+
* @deprecated Use
49+
* {@link DefaultSaml2AuthenticationRequestContextResolver#DefaultSaml2AuthenticationRequestContextResolver(RelyingPartyRegistrationResolver)}
50+
* instead
51+
*/
52+
@Deprecated
4553
public DefaultSaml2AuthenticationRequestContextResolver(
4654
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
4755
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
4856
}
4957

58+
public DefaultSaml2AuthenticationRequestContextResolver(
59+
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
60+
this.relyingPartyRegistrationResolver = (request) -> relyingPartyRegistrationResolver.resolve(request, null);
61+
}
62+
5063
@Override
5164
public Saml2AuthenticationRequestContext resolve(HttpServletRequest request) {
5265
Assert.notNull(request, "request cannot be null");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.web;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
22+
23+
/**
24+
* A contract for resolving a {@link RelyingPartyRegistration} from the HTTP request
25+
*
26+
* @author Josh Cummings
27+
* @since 5.6
28+
*/
29+
public interface RelyingPartyRegistrationResolver {
30+
31+
/**
32+
* Resolve a {@link RelyingPartyRegistration} from the HTTP request, using the
33+
* {@code relyingPartyRegistrationId}, if it is provided
34+
* @param request the HTTP request
35+
* @param relyingPartyRegistrationId the {@link RelyingPartyRegistration} identifier
36+
* @return the resolved {@link RelyingPartyRegistration}
37+
*/
38+
RelyingPartyRegistration resolve(HttpServletRequest request, String relyingPartyRegistrationId);
39+
40+
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2AuthenticationTokenConverter.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,28 @@ public final class Saml2AuthenticationTokenConverter implements AuthenticationCo
6161
* resolving {@link RelyingPartyRegistration}s
6262
* @param relyingPartyRegistrationResolver the strategy for resolving
6363
* {@link RelyingPartyRegistration}s
64+
* @deprecated Use
65+
* {@link Saml2AuthenticationTokenConverter#Saml2AuthenticationTokenConverter(RelyingPartyRegistrationResolver)}
66+
* instead
6467
*/
68+
@Deprecated
6569
public Saml2AuthenticationTokenConverter(
6670
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver) {
6771
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
6872
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
6973
this.loader = new HttpSessionSaml2AuthenticationRequestRepository()::loadAuthenticationRequest;
7074
}
7175

76+
public Saml2AuthenticationTokenConverter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
77+
this(adaptToConverter(relyingPartyRegistrationResolver));
78+
}
79+
80+
private static Converter<HttpServletRequest, RelyingPartyRegistration> adaptToConverter(
81+
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
82+
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
83+
return (request) -> relyingPartyRegistrationResolver.resolve(request, null);
84+
}
85+
7286
@Override
7387
public Saml2AuthenticationToken convert(HttpServletRequest request) {
7488
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationResolver.convert(request);

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/Saml2MetadataFilter.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
4646

4747
public static final String DEFAULT_METADATA_FILE_NAME = "saml-{registrationId}-metadata.xml";
4848

49-
private final Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationConverter;
49+
private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
5050

5151
private final Saml2MetadataResolver saml2MetadataResolver;
5252

@@ -55,11 +55,26 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
5555
private RequestMatcher requestMatcher = new AntPathRequestMatcher(
5656
"/saml2/service-provider-metadata/{registrationId}");
5757

58-
public Saml2MetadataFilter(
59-
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationConverter,
58+
/**
59+
* Construct a {@link Saml2MetadataFilter}
60+
* @param relyingPartyRegistrationResolver
61+
* @param saml2MetadataResolver
62+
* @deprecated Use
63+
* {@link Saml2MetadataFilter#Saml2MetadataFilter(RelyingPartyRegistrationResolver)}
64+
* instead
65+
*/
66+
@Deprecated
67+
public Saml2MetadataFilter(Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver,
6068
Saml2MetadataResolver saml2MetadataResolver) {
69+
this.relyingPartyRegistrationResolver = (request, id) -> relyingPartyRegistrationResolver.convert(request);
70+
this.saml2MetadataResolver = saml2MetadataResolver;
71+
}
6172

62-
this.relyingPartyRegistrationConverter = relyingPartyRegistrationConverter;
73+
public Saml2MetadataFilter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver,
74+
Saml2MetadataResolver saml2MetadataResolver) {
75+
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
76+
Assert.notNull(saml2MetadataResolver, "saml2MetadataResolver cannot be null");
77+
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
6378
this.saml2MetadataResolver = saml2MetadataResolver;
6479
}
6580

@@ -71,14 +86,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
7186
chain.doFilter(request, response);
7287
return;
7388
}
74-
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationConverter.convert(request);
89+
String registrationId = matcher.getVariables().get("registrationId");
90+
RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationResolver.resolve(request,
91+
registrationId);
7592
if (relyingPartyRegistration == null) {
7693
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
7794
return;
7895
}
7996
String metadata = this.saml2MetadataResolver.resolve(relyingPartyRegistration);
80-
String registrationId = relyingPartyRegistration.getRegistrationId();
81-
writeMetadataToResponse(response, registrationId, metadata);
97+
writeMetadataToResponse(response, relyingPartyRegistration.getRegistrationId(), metadata);
8298
}
8399

84100
private void writeMetadataToResponse(HttpServletResponse response, String registrationId, String metadata)

0 commit comments

Comments
 (0)