Skip to content

Commit 11aa02c

Browse files
committed
Separate SAML 2.0 Login Docs
Issue gh-10367
1 parent 6d2d3b9 commit 11aa02c

File tree

6 files changed

+713
-709
lines changed

6 files changed

+713
-709
lines changed

docs/modules/ROOT/nav.adoc

+4-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@
6363
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
6464
**** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
6565
** xref:servlet/saml2/index.adoc[SAML2]
66-
*** xref:servlet/saml2/login.adoc[SAML2 Log In]
66+
*** xref:servlet/saml2/login/index.adoc[SAML2 Log In]
67+
**** xref:servlet/saml2/login/overview.adoc[SAML2 Log In Overview]
68+
**** xref:servlet/saml2/login/authentication-requests.adoc[SAML2 Authentication Requests]
69+
**** xref:servlet/saml2/login/authentication.adoc[SAML2 Authentication Responses]
6770
*** xref:servlet/saml2/logout.adoc[SAML2 Logout]
6871
*** xref:servlet/saml2/metadata.adoc[SAML2 Metadata]
6972
** xref:servlet/exploits/index.adoc[Protection Against Exploits]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
[[servlet-saml2login-sp-initiated-factory]]
2+
= Producing ``<saml2:AuthnRequest>``s
3+
4+
As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party.
5+
6+
Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain.
7+
This filter by default responds to endpoint `+/saml2/authenticate/{registrationId}+`.
8+
9+
For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
10+
11+
`https://rp.example.org/saml2/authenticate/ping`
12+
13+
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
14+
15+
[[servlet-saml2login-store-authn-request]]
16+
== Changing How the `<saml2:AuthnRequest>` Gets Stored
17+
18+
`Saml2WebSsoAuthenticationRequestFilter` uses an `Saml2AuthenticationRequestRepository` to persist an `AbstractSaml2AuthenticationRequest` instance before xref:servlet/saml2/login/authentication-requests.adoc#servlet-saml2login-sp-initiated-factory[sending the `<saml2:AuthnRequest>`] to the asserting party.
19+
20+
Additionally, `Saml2WebSsoAuthenticationFilter` and `Saml2AuthenticationTokenConverter` use an `Saml2AuthenticationRequestRepository` to load any `AbstractSaml2AuthenticationRequest` as part of xref:servlet/saml2/login/authentication.adoc#servlet-saml2login-authenticate-responses[authenticating the `<saml2:Response>`].
21+
22+
By default, Spring Security uses an `HttpSessionSaml2AuthenticationRequestRepository`, which stores the `AbstractSaml2AuthenticationRequest` in the `HttpSession`.
23+
24+
If you have a custom implementation of `Saml2AuthenticationRequestRepository`, you may configure it by exposing it as a `@Bean` as shown in the following example:
25+
26+
====
27+
.Java
28+
[source,java,role="primary"]
29+
----
30+
@Bean
31+
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
32+
return new CustomSaml2AuthenticationRequestRepository();
33+
}
34+
----
35+
36+
.Kotlin
37+
[source,kotlin,role="secondary"]
38+
----
39+
@Bean
40+
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
41+
return CustomSaml2AuthenticationRequestRepository()
42+
}
43+
----
44+
====
45+
46+
[[servlet-saml2login-sp-initiated-factory-signing]]
47+
== Changing How the `<saml2:AuthnRequest>` Gets Sent
48+
49+
By default, Spring Security signs each `<saml2:AuthnRequest>` and send it as a GET to the asserting party.
50+
51+
Many asserting parties don't require a signed `<saml2:AuthnRequest>`.
52+
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
53+
54+
55+
.Not Requiring Signed AuthnRequests
56+
====
57+
.Boot
58+
[source,yaml,role="primary"]
59+
----
60+
spring:
61+
security:
62+
saml2:
63+
relyingparty:
64+
okta:
65+
identityprovider:
66+
entity-id: ...
67+
singlesignon.sign-request: false
68+
----
69+
70+
.Java
71+
[source,java,role="secondary"]
72+
----
73+
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
74+
// ...
75+
.assertingPartyDetails(party -> party
76+
// ...
77+
.wantAuthnRequestsSigned(false)
78+
)
79+
.build();
80+
----
81+
82+
.Kotlin
83+
[source,java,role="secondary"]
84+
----
85+
var relyingPartyRegistration: RelyingPartyRegistration =
86+
RelyingPartyRegistration.withRegistrationId("okta")
87+
// ...
88+
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
89+
// ...
90+
.wantAuthnRequestsSigned(false)
91+
}
92+
.build();
93+
----
94+
====
95+
96+
Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending.
97+
98+
[[servlet-saml2login-sp-initiated-factory-algorithm]]
99+
By default, Spring Security will sign the `<saml2:AuthnRequest>` using `rsa-sha256`, though some asserting parties will require a different algorithm, as indicated in their metadata.
100+
101+
You can configure the algorithm based on the asserting party's xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[metadata using `RelyingPartyRegistrations`].
102+
103+
Or, you can provide it manually:
104+
105+
====
106+
.Java
107+
[source,java,role="primary"]
108+
----
109+
String metadataLocation = "classpath:asserting-party-metadata.xml";
110+
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
111+
// ...
112+
.assertingPartyDetails((party) -> party
113+
// ...
114+
.signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
115+
)
116+
.build();
117+
----
118+
119+
.Kotlin
120+
[source,kotlin,role="secondary"]
121+
----
122+
var metadataLocation = "classpath:asserting-party-metadata.xml"
123+
var relyingPartyRegistration: RelyingPartyRegistration =
124+
RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
125+
// ...
126+
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
127+
// ...
128+
.signingAlgorithms { sign: MutableList<String?> ->
129+
sign.add(
130+
SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
131+
)
132+
}
133+
}
134+
.build();
135+
----
136+
====
137+
138+
NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
139+
But, that's just for convenience.
140+
Since the datatype is `String`, you can supply the name of the algorithm directly.
141+
142+
[[servlet-saml2login-sp-initiated-factory-binding]]
143+
Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
144+
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
145+
146+
====
147+
.Java
148+
[source,java,role="primary"]
149+
----
150+
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
151+
// ...
152+
.assertingPartyDetails(party -> party
153+
// ...
154+
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
155+
)
156+
.build();
157+
----
158+
159+
.Kotlin
160+
[source,kotlin,role="secondary"]
161+
----
162+
var relyingPartyRegistration: RelyingPartyRegistration? =
163+
RelyingPartyRegistration.withRegistrationId("okta")
164+
// ...
165+
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
166+
// ...
167+
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
168+
}
169+
.build()
170+
----
171+
====
172+
173+
[[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
174+
== Customizing OpenSAML's `AuthnRequest` Instance
175+
176+
There are a number of reasons that you may want to adjust an `AuthnRequest`.
177+
For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
178+
179+
If you don't need information from the `HttpServletRequest` to make your decision, then the easiest way is to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-opensaml-customization[register a custom `AuthnRequestMarshaller` with OpenSAML].
180+
This will give you access to post-process the `AuthnRequest` instance before it's serialized.
181+
182+
But, if you do need something from the request, then you can use create a custom `Saml2AuthenticationRequestContext` implementation and then a `Converter<Saml2AuthenticationRequestContext, AuthnRequest>` to build an `AuthnRequest` yourself, like so:
183+
184+
====
185+
.Java
186+
[source,java,role="primary"]
187+
----
188+
@Component
189+
public class AuthnRequestConverter implements
190+
Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
191+
192+
private final AuthnRequestBuilder authnRequestBuilder;
193+
private final IssuerBuilder issuerBuilder;
194+
195+
// ... constructor
196+
197+
public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
198+
MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
199+
Issuer issuer = issuerBuilder.buildObject();
200+
issuer.setValue(myContext.getIssuer());
201+
202+
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
203+
authnRequest.setIssuer(issuer);
204+
authnRequest.setDestination(myContext.getDestination());
205+
authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());
206+
207+
// ... additional settings
208+
209+
authRequest.setForceAuthn(myContext.getForceAuthn());
210+
return authnRequest;
211+
}
212+
}
213+
----
214+
215+
.Kotlin
216+
[source,kotlin,role="secondary"]
217+
----
218+
@Component
219+
class AuthnRequestConverter : Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
220+
private val authnRequestBuilder: AuthnRequestBuilder? = null
221+
private val issuerBuilder: IssuerBuilder? = null
222+
223+
// ... constructor
224+
override fun convert(context: MySaml2AuthenticationRequestContext): AuthnRequest {
225+
val myContext: MySaml2AuthenticationRequestContext = context
226+
val issuer: Issuer = issuerBuilder.buildObject()
227+
issuer.value = myContext.getIssuer()
228+
val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
229+
authnRequest.issuer = issuer
230+
authnRequest.destination = myContext.getDestination()
231+
authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()
232+
233+
// ... additional settings
234+
authRequest.setForceAuthn(myContext.getForceAuthn())
235+
return authnRequest
236+
}
237+
}
238+
----
239+
====
240+
241+
Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as ``@Bean``s:
242+
243+
====
244+
.Java
245+
[source,java,role="primary"]
246+
----
247+
@Bean
248+
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
249+
Saml2AuthenticationRequestContextResolver resolver =
250+
new DefaultSaml2AuthenticationRequestContextResolver();
251+
return request -> {
252+
Saml2AuthenticationRequestContext context = resolver.resolve(request);
253+
return new MySaml2AuthenticationRequestContext(context, request.getParameter("force") != null);
254+
};
255+
}
256+
257+
@Bean
258+
Saml2AuthenticationRequestFactory authenticationRequestFactory(
259+
AuthnRequestConverter authnRequestConverter) {
260+
261+
OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
262+
new OpenSaml4AuthenticationRequestFactory();
263+
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
264+
return authenticationRequestFactory;
265+
}
266+
----
267+
268+
.Kotlin
269+
[source,kotlin,role="secondary"]
270+
----
271+
@Bean
272+
open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
273+
val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
274+
return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
275+
val context = resolver.resolve(request)
276+
MySaml2AuthenticationRequestContext(
277+
context,
278+
request.getParameter("force") != null
279+
)
280+
}
281+
}
282+
283+
@Bean
284+
open fun authenticationRequestFactory(
285+
authnRequestConverter: AuthnRequestConverter?
286+
): Saml2AuthenticationRequestFactory? {
287+
val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
288+
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
289+
return authenticationRequestFactory
290+
}
291+
----
292+
====
293+

0 commit comments

Comments
 (0)