authentication-requests.adoc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. [[servlet-saml2login-sp-initiated-factory]]
  2. = Producing ``<saml2:AuthnRequest>``s
  3. As stated earlier, Spring Security's SAML 2.0 support produces a `<saml2:AuthnRequest>` to commence authentication with the asserting party.
  4. Spring Security achieves this in part by registering the `Saml2WebSsoAuthenticationRequestFilter` in the filter chain.
  5. This filter by default responds to the endpoints `+/saml2/authenticate/{registrationId}+` and `+/saml2/authenticate?registrationId={registrationId}+`.
  6. For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
  7. `https://rp.example.org/saml2/authenticate/okta`
  8. and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
  9. [[configuring-authentication-request-uri]]
  10. == Configuring the `<saml2:AuthnRequest>` Endpoint
  11. To configure the endpoint differently from the default, you can set the value in `saml2Login`:
  12. [tabs]
  13. ======
  14. Java::
  15. +
  16. [source,java,role="primary"]
  17. ----
  18. @Bean
  19. SecurityFilterChain filterChain(HttpSecurity http) {
  20. http
  21. .saml2Login((saml2) -> saml2
  22. .authenticationRequestUriQuery("/custom/auth/sso?peerEntityID={registrationId}")
  23. );
  24. return new CustomSaml2AuthenticationRequestRepository();
  25. }
  26. ----
  27. Kotlin::
  28. +
  29. [source,kotlin,role="secondary"]
  30. ----
  31. @Bean
  32. fun filterChain(http: HttpSecurity): SecurityFilterChain {
  33. http {
  34. saml2Login {
  35. authenticationRequestUriQuery = "/custom/auth/sso?peerEntityID={registrationId}"
  36. }
  37. }
  38. return CustomSaml2AuthenticationRequestRepository()
  39. }
  40. ----
  41. ======
  42. [[servlet-saml2login-store-authn-request]]
  43. == Changing How the `<saml2:AuthnRequest>` Gets Stored
  44. `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.
  45. 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>`].
  46. By default, Spring Security uses an `HttpSessionSaml2AuthenticationRequestRepository`, which stores the `AbstractSaml2AuthenticationRequest` in the `HttpSession`.
  47. If you have a custom implementation of `Saml2AuthenticationRequestRepository`, you may configure it by exposing it as a `@Bean` as shown in the following example:
  48. [tabs]
  49. ======
  50. Java::
  51. +
  52. [source,java,role="primary"]
  53. ----
  54. @Bean
  55. Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
  56. return new CustomSaml2AuthenticationRequestRepository();
  57. }
  58. ----
  59. Kotlin::
  60. +
  61. [source,kotlin,role="secondary"]
  62. ----
  63. @Bean
  64. open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
  65. return CustomSaml2AuthenticationRequestRepository()
  66. }
  67. ----
  68. ======
  69. === Caching the `<saml2:AuthnRequest>` by the Relay State
  70. If you don't want to use the session to store the `<saml2:AuthnRequest>`, you can also store it in a distributed cache.
  71. 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.
  72. [NOTE]
  73. =====
  74. It's important to remember that there are security benefits to storing it in the session.
  75. One such benefit is the natural login fixation defense it provides.
  76. 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.
  77. 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.
  78. =====
  79. To help with this, Spring Security has `CacheSaml2AuthenticationRequestRepository`, which you can publish as a bean for the filter chain to pick up:
  80. [tabs]
  81. ======
  82. Java::
  83. +
  84. [source,java,role="primary"]
  85. ----
  86. @Bean
  87. Saml2AuthenticationRequestRepository<?> authenticationRequestRepository() {
  88. return new CacheSaml2AuthenticationRequestRepository();
  89. }
  90. ----
  91. Kotlin::
  92. +
  93. [source,kotlin,role="secondary"]
  94. ----
  95. @Bean
  96. fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<*> {
  97. return CacheSaml2AuthenticationRequestRepository()
  98. }
  99. ----
  100. ======
  101. [[servlet-saml2login-sp-initiated-factory-signing]]
  102. == Changing How the `<saml2:AuthnRequest>` Gets Sent
  103. By default, Spring Security signs each `<saml2:AuthnRequest>` and send it as a GET to the asserting party.
  104. Many asserting parties don't require a signed `<saml2:AuthnRequest>`.
  105. This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
  106. .Not Requiring Signed AuthnRequests
  107. [tabs]
  108. ======
  109. Boot::
  110. +
  111. [source,yaml,role="primary"]
  112. ----
  113. spring:
  114. security:
  115. saml2:
  116. relyingparty:
  117. registration:
  118. okta:
  119. assertingparty:
  120. entity-id: ...
  121. singlesignon.sign-request: false
  122. ----
  123. Java::
  124. +
  125. [source,java,role="secondary"]
  126. ----
  127. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
  128. // ...
  129. .assertingPartyMetadata((party) -> party
  130. // ...
  131. .wantAuthnRequestsSigned(false)
  132. )
  133. .build();
  134. ----
  135. Kotlin::
  136. +
  137. [source,kotlin,role="secondary"]
  138. ----
  139. var relyingPartyRegistration: RelyingPartyRegistration =
  140. RelyingPartyRegistration.withRegistrationId("okta")
  141. // ...
  142. .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
  143. // ...
  144. .wantAuthnRequestsSigned(false)
  145. }
  146. .build()
  147. ----
  148. ======
  149. Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending.
  150. [[servlet-saml2login-sp-initiated-factory-algorithm]]
  151. 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.
  152. You can configure the algorithm based on the asserting party's xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[metadata using `RelyingPartyRegistrations`].
  153. Or, you can provide it manually:
  154. [tabs]
  155. ======
  156. Java::
  157. +
  158. [source,java,role="primary"]
  159. ----
  160. String metadataLocation = "classpath:asserting-party-metadata.xml";
  161. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
  162. // ...
  163. .assertingPartyMetadata((party) -> party
  164. // ...
  165. .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
  166. )
  167. .build();
  168. ----
  169. Kotlin::
  170. +
  171. [source,kotlin,role="secondary"]
  172. ----
  173. var metadataLocation = "classpath:asserting-party-metadata.xml"
  174. var relyingPartyRegistration: RelyingPartyRegistration =
  175. RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
  176. // ...
  177. .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
  178. // ...
  179. .signingAlgorithms { sign: MutableList<String?> ->
  180. sign.add(
  181. SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
  182. )
  183. }
  184. }
  185. .build()
  186. ----
  187. ======
  188. NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
  189. But, that's just for convenience.
  190. Since the datatype is `String`, you can supply the name of the algorithm directly.
  191. [[servlet-saml2login-sp-initiated-factory-binding]]
  192. Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
  193. This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
  194. [tabs]
  195. ======
  196. Java::
  197. +
  198. [source,java,role="primary"]
  199. ----
  200. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
  201. // ...
  202. .assertingPartyMetadata((party) -> party
  203. // ...
  204. .singleSignOnServiceBinding(Saml2MessageBinding.POST)
  205. )
  206. .build();
  207. ----
  208. Kotlin::
  209. +
  210. [source,kotlin,role="secondary"]
  211. ----
  212. var relyingPartyRegistration: RelyingPartyRegistration? =
  213. RelyingPartyRegistration.withRegistrationId("okta")
  214. // ...
  215. .assertingPartyMetadata { party: AssertingPartyMetadata.Builder -> party
  216. // ...
  217. .singleSignOnServiceBinding(Saml2MessageBinding.POST)
  218. }
  219. .build()
  220. ----
  221. ======
  222. [[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
  223. == Customizing OpenSAML's `AuthnRequest` Instance
  224. There are a number of reasons that you may want to adjust an `AuthnRequest`.
  225. For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
  226. You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml5AuthenticationRequestResolver` as a `@Bean`, like so:
  227. [tabs]
  228. ======
  229. Java::
  230. +
  231. [source,java,role="primary"]
  232. ----
  233. @Bean
  234. Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
  235. RelyingPartyRegistrationResolver registrationResolver =
  236. new DefaultRelyingPartyRegistrationResolver(registrations);
  237. OpenSaml5AuthenticationRequestResolver authenticationRequestResolver =
  238. new OpenSaml5AuthenticationRequestResolver(registrationResolver);
  239. authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
  240. .getAuthnRequest().setForceAuthn(true));
  241. return authenticationRequestResolver;
  242. }
  243. ----
  244. Kotlin::
  245. +
  246. [source,kotlin,role="secondary"]
  247. ----
  248. @Bean
  249. fun authenticationRequestResolver(registrations : RelyingPartyRegistrationRepository) : Saml2AuthenticationRequestResolver {
  250. val registrationResolver : RelyingPartyRegistrationResolver =
  251. new DefaultRelyingPartyRegistrationResolver(registrations)
  252. val authenticationRequestResolver : OpenSaml5AuthenticationRequestResolver =
  253. new OpenSaml5AuthenticationRequestResolver(registrationResolver)
  254. authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
  255. .getAuthnRequest().setForceAuthn(true))
  256. return authenticationRequestResolver
  257. }
  258. ----
  259. ======