123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- [[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 `OpenSaml5AuthenticationRequestResolver` as a `@Bean`, like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
- RelyingPartyRegistrationResolver registrationResolver =
- new DefaultRelyingPartyRegistrationResolver(registrations);
- OpenSaml5AuthenticationRequestResolver authenticationRequestResolver =
- new OpenSaml5AuthenticationRequestResolver(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 : OpenSaml5AuthenticationRequestResolver =
- new OpenSaml5AuthenticationRequestResolver(registrationResolver)
- authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
- .getAuthnRequest().setForceAuthn(true))
- return authenticationRequestResolver
- }
- ----
- ======
|