You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
10 KiB
306 lines
10 KiB
[[servlet-saml2login-sp-initiated-factory]] |
|
= Producing ``<saml2:AuthnRequest>``s |
|
|
|
As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party. |
|
|
|
Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain. |
|
This filter by default responds to the endpoints `+/saml2/authenticate/{registrationId}+` and `+/saml2/authenticate?registrationId={registrationId}+`. |
|
|
|
For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to: |
|
|
|
`https://rp.example.org/saml2/authenticate/okta` |
|
|
|
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`. |
|
|
|
[[configuring-authentication-request-uri]] |
|
== Configuring the `<saml2:AuthnRequest>` Endpoint |
|
|
|
To configure the endpoint differently from the default, you can set the value in `saml2Login`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
SecurityFilterChain filterChain(HttpSecurity http) { |
|
http |
|
.saml2Login((saml2) -> saml2 |
|
.authenticationRequestUriQuery("/custom/auth/sso?peerEntityID={registrationId}") |
|
); |
|
return new CustomSaml2AuthenticationRequestRepository(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun filterChain(http: HttpSecurity): SecurityFilterChain { |
|
http { |
|
saml2Login { |
|
authenticationRequestUriQuery = "/custom/auth/sso?peerEntityID={registrationId}" |
|
} |
|
} |
|
return CustomSaml2AuthenticationRequestRepository() |
|
} |
|
---- |
|
====== |
|
|
|
[[servlet-saml2login-store-authn-request]] |
|
== Changing How the `<saml2:AuthnRequest>` Gets Stored |
|
|
|
`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. |
|
|
|
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>`]. |
|
|
|
By default, Spring Security uses an `HttpSessionSaml2AuthenticationRequestRepository`, which stores the `AbstractSaml2AuthenticationRequest` in the `HttpSession`. |
|
|
|
If you have a custom implementation of `Saml2AuthenticationRequestRepository`, you may configure it by exposing it as a `@Bean` as shown in the following example: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() { |
|
return new CustomSaml2AuthenticationRequestRepository(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> { |
|
return CustomSaml2AuthenticationRequestRepository() |
|
} |
|
---- |
|
====== |
|
|
|
=== Caching the `<saml2:AuthnRequest>` by the Relay State |
|
|
|
If you don't want to use the session to store the `<saml2:AuthnRequest>`, you can also store it in a distributed cache. |
|
This can be helpful if you are trying to use `SameSite=Strict` and are losing the authentication request in the redirect from the Identity Provider. |
|
|
|
[NOTE] |
|
===== |
|
It's important to remember that there are security benefits to storing it in the session. |
|
One such benefit is the natural login fixation defense it provides. |
|
For example, if an application looks the authentication request up from the session, then even if an attacker provides their own SAML response to a victim, the login will fail. |
|
|
|
On the other hand, if we trust the InResponseTo or RelayState to retrieve the authentication request, then there's no way to know if the SAML response was requested by that handshake. |
|
===== |
|
|
|
To help with this, Spring Security has `CacheSaml2AuthenticationRequestRepository`, which you can publish as a bean for the filter chain to pick up: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
Saml2AuthenticationRequestRepository<?> authenticationRequestRepository() { |
|
return new CacheSaml2AuthenticationRequestRepository(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<*> { |
|
return CacheSaml2AuthenticationRequestRepository() |
|
} |
|
---- |
|
====== |
|
|
|
|
|
[[servlet-saml2login-sp-initiated-factory-signing]] |
|
== Changing How the `<saml2:AuthnRequest>` Gets Sent |
|
|
|
By default, Spring Security signs each `<saml2:AuthnRequest>` and send it as a GET to the asserting party. |
|
|
|
Many asserting parties don't require a signed `<saml2:AuthnRequest>`. |
|
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so: |
|
|
|
|
|
.Not Requiring Signed AuthnRequests |
|
[tabs] |
|
====== |
|
Boot:: |
|
+ |
|
[source,yaml,role="primary"] |
|
---- |
|
spring: |
|
security: |
|
saml2: |
|
relyingparty: |
|
registration: |
|
okta: |
|
assertingparty: |
|
entity-id: ... |
|
singlesignon.sign-request: false |
|
---- |
|
|
|
Java:: |
|
+ |
|
[source,java,role="secondary"] |
|
---- |
|
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta") |
|
// ... |
|
.assertingPartyMetadata((party) -> party |
|
// ... |
|
.wantAuthnRequestsSigned(false) |
|
) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
var relyingPartyRegistration: RelyingPartyRegistration = |
|
RelyingPartyRegistration.withRegistrationId("okta") |
|
// ... |
|
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party |
|
// ... |
|
.wantAuthnRequestsSigned(false) |
|
} |
|
.build() |
|
---- |
|
====== |
|
|
|
Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending. |
|
|
|
[[servlet-saml2login-sp-initiated-factory-algorithm]] |
|
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. |
|
|
|
You can configure the algorithm based on the asserting party's xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[metadata using `RelyingPartyRegistrations`]. |
|
|
|
Or, you can provide it manually: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
String metadataLocation = "classpath:asserting-party-metadata.xml"; |
|
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation) |
|
// ... |
|
.assertingPartyMetadata((party) -> party |
|
// ... |
|
.signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512)) |
|
) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
var metadataLocation = "classpath:asserting-party-metadata.xml" |
|
var relyingPartyRegistration: RelyingPartyRegistration = |
|
RelyingPartyRegistrations.fromMetadataLocation(metadataLocation) |
|
// ... |
|
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party |
|
// ... |
|
.signingAlgorithms { sign: MutableList<String?> -> |
|
sign.add( |
|
SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512 |
|
) |
|
} |
|
} |
|
.build() |
|
---- |
|
====== |
|
|
|
NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name. |
|
But, that's just for convenience. |
|
Since the datatype is `String`, you can supply the name of the algorithm directly. |
|
|
|
[[servlet-saml2login-sp-initiated-factory-binding]] |
|
Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed. |
|
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta") |
|
// ... |
|
.assertingPartyMetadata((party) -> party |
|
// ... |
|
.singleSignOnServiceBinding(Saml2MessageBinding.POST) |
|
) |
|
.build(); |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
var relyingPartyRegistration: RelyingPartyRegistration? = |
|
RelyingPartyRegistration.withRegistrationId("okta") |
|
// ... |
|
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party |
|
// ... |
|
.singleSignOnServiceBinding(Saml2MessageBinding.POST) |
|
} |
|
.build() |
|
---- |
|
====== |
|
|
|
[[servlet-saml2login-sp-initiated-factory-custom-authnrequest]] |
|
== Customizing OpenSAML's `AuthnRequest` Instance |
|
|
|
There are a number of reasons that you may want to adjust an `AuthnRequest`. |
|
For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default. |
|
|
|
You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml4AuthenticationRequestResolver` as a `@Bean`, like so: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) { |
|
RelyingPartyRegistrationResolver registrationResolver = |
|
new DefaultRelyingPartyRegistrationResolver(registrations); |
|
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = |
|
new OpenSaml4AuthenticationRequestResolver(registrationResolver); |
|
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context |
|
.getAuthnRequest().setForceAuthn(true)); |
|
return authenticationRequestResolver; |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun authenticationRequestResolver(registrations : RelyingPartyRegistrationRepository) : Saml2AuthenticationRequestResolver { |
|
val registrationResolver : RelyingPartyRegistrationResolver = |
|
new DefaultRelyingPartyRegistrationResolver(registrations) |
|
val authenticationRequestResolver : OpenSaml4AuthenticationRequestResolver = |
|
new OpenSaml4AuthenticationRequestResolver(registrationResolver) |
|
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context |
|
.getAuthnRequest().setForceAuthn(true)) |
|
return authenticationRequestResolver |
|
} |
|
---- |
|
====== |
|
|
|
|