authentication-requests.adoc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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 endpoint `+/saml2/authenticate/{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/ping`
  8. and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
  9. [[servlet-saml2login-store-authn-request]]
  10. == Changing How the `<saml2:AuthnRequest>` Gets Stored
  11. `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.
  12. 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>`].
  13. By default, Spring Security uses an `HttpSessionSaml2AuthenticationRequestRepository`, which stores the `AbstractSaml2AuthenticationRequest` in the `HttpSession`.
  14. If you have a custom implementation of `Saml2AuthenticationRequestRepository`, you may configure it by exposing it as a `@Bean` as shown in the following example:
  15. ====
  16. .Java
  17. [source,java,role="primary"]
  18. ----
  19. @Bean
  20. Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
  21. return new CustomSaml2AuthenticationRequestRepository();
  22. }
  23. ----
  24. .Kotlin
  25. [source,kotlin,role="secondary"]
  26. ----
  27. @Bean
  28. open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
  29. return CustomSaml2AuthenticationRequestRepository()
  30. }
  31. ----
  32. ====
  33. [[servlet-saml2login-sp-initiated-factory-signing]]
  34. == Changing How the `<saml2:AuthnRequest>` Gets Sent
  35. By default, Spring Security signs each `<saml2:AuthnRequest>` and send it as a GET to the asserting party.
  36. Many asserting parties don't require a signed `<saml2:AuthnRequest>`.
  37. This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
  38. .Not Requiring Signed AuthnRequests
  39. ====
  40. .Boot
  41. [source,yaml,role="primary"]
  42. ----
  43. spring:
  44. security:
  45. saml2:
  46. relyingparty:
  47. okta:
  48. identityprovider:
  49. entity-id: ...
  50. singlesignon.sign-request: false
  51. ----
  52. .Java
  53. [source,java,role="secondary"]
  54. ----
  55. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
  56. // ...
  57. .assertingPartyDetails(party -> party
  58. // ...
  59. .wantAuthnRequestsSigned(false)
  60. )
  61. .build();
  62. ----
  63. .Kotlin
  64. [source,java,role="secondary"]
  65. ----
  66. var relyingPartyRegistration: RelyingPartyRegistration =
  67. RelyingPartyRegistration.withRegistrationId("okta")
  68. // ...
  69. .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
  70. // ...
  71. .wantAuthnRequestsSigned(false)
  72. }
  73. .build();
  74. ----
  75. ====
  76. Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending.
  77. [[servlet-saml2login-sp-initiated-factory-algorithm]]
  78. 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.
  79. You can configure the algorithm based on the asserting party's xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[metadata using `RelyingPartyRegistrations`].
  80. Or, you can provide it manually:
  81. ====
  82. .Java
  83. [source,java,role="primary"]
  84. ----
  85. String metadataLocation = "classpath:asserting-party-metadata.xml";
  86. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
  87. // ...
  88. .assertingPartyDetails((party) -> party
  89. // ...
  90. .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
  91. )
  92. .build();
  93. ----
  94. .Kotlin
  95. [source,kotlin,role="secondary"]
  96. ----
  97. var metadataLocation = "classpath:asserting-party-metadata.xml"
  98. var relyingPartyRegistration: RelyingPartyRegistration =
  99. RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
  100. // ...
  101. .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
  102. // ...
  103. .signingAlgorithms { sign: MutableList<String?> ->
  104. sign.add(
  105. SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
  106. )
  107. }
  108. }
  109. .build();
  110. ----
  111. ====
  112. NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
  113. But, that's just for convenience.
  114. Since the datatype is `String`, you can supply the name of the algorithm directly.
  115. [[servlet-saml2login-sp-initiated-factory-binding]]
  116. Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
  117. This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
  118. ====
  119. .Java
  120. [source,java,role="primary"]
  121. ----
  122. RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
  123. // ...
  124. .assertingPartyDetails(party -> party
  125. // ...
  126. .singleSignOnServiceBinding(Saml2MessageBinding.POST)
  127. )
  128. .build();
  129. ----
  130. .Kotlin
  131. [source,kotlin,role="secondary"]
  132. ----
  133. var relyingPartyRegistration: RelyingPartyRegistration? =
  134. RelyingPartyRegistration.withRegistrationId("okta")
  135. // ...
  136. .assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
  137. // ...
  138. .singleSignOnServiceBinding(Saml2MessageBinding.POST)
  139. }
  140. .build()
  141. ----
  142. ====
  143. [[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
  144. == Customizing OpenSAML's `AuthnRequest` Instance
  145. There are a number of reasons that you may want to adjust an `AuthnRequest`.
  146. For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
  147. 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].
  148. This will give you access to post-process the `AuthnRequest` instance before it's serialized.
  149. 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:
  150. ====
  151. .Java
  152. [source,java,role="primary"]
  153. ----
  154. @Component
  155. public class AuthnRequestConverter implements
  156. Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
  157. private final AuthnRequestBuilder authnRequestBuilder;
  158. private final IssuerBuilder issuerBuilder;
  159. // ... constructor
  160. public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
  161. Issuer issuer = issuerBuilder.buildObject();
  162. issuer.setValue(context.getIssuer());
  163. AuthnRequest authnRequest = authnRequestBuilder.buildObject();
  164. authnRequest.setIssuer(issuer);
  165. authnRequest.setDestination(context.getDestination());
  166. authnRequest.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl());
  167. // ... additional settings
  168. authRequest.setForceAuthn(context.getForceAuthn());
  169. return authnRequest;
  170. }
  171. }
  172. ----
  173. .Kotlin
  174. [source,kotlin,role="secondary"]
  175. ----
  176. @Component
  177. class AuthnRequestConverter : Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
  178. private val authnRequestBuilder: AuthnRequestBuilder? = null
  179. private val issuerBuilder: IssuerBuilder? = null
  180. // ... constructor
  181. override fun convert(context: MySaml2AuthenticationRequestContext): AuthnRequest {
  182. val issuer: Issuer = issuerBuilder.buildObject()
  183. issuer.value = context.getIssuer()
  184. val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
  185. authnRequest.issuer = issuer
  186. authnRequest.destination = context.getDestination()
  187. authnRequest.assertionConsumerServiceURL = context.getAssertionConsumerServiceUrl()
  188. // ... additional settings
  189. authRequest.setForceAuthn(context.getForceAuthn())
  190. return authnRequest
  191. }
  192. }
  193. ----
  194. ====
  195. Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as ``@Bean``s:
  196. ====
  197. .Java
  198. [source,java,role="primary"]
  199. ----
  200. @Bean
  201. Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
  202. Saml2AuthenticationRequestContextResolver resolver = new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
  203. return request -> {
  204. Saml2AuthenticationRequestContext context = resolver.resolve(request);
  205. return context;
  206. };
  207. }
  208. @Bean
  209. Saml2AuthenticationRequestFactory authenticationRequestFactory(
  210. AuthnRequestConverter authnRequestConverter) {
  211. OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
  212. new OpenSaml4AuthenticationRequestFactory();
  213. authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
  214. return authenticationRequestFactory;
  215. }
  216. ----
  217. .Kotlin
  218. [source,kotlin,role="secondary"]
  219. ----
  220. @Bean
  221. open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
  222. val resolver = DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver)
  223. return Saml2AuthenticationRequestContextResolver { request ->
  224. resolver.resolve(request)
  225. }
  226. }
  227. @Bean
  228. open fun authenticationRequestFactory(
  229. authnRequestConverter: AuthnRequestConverter?
  230. ): Saml2AuthenticationRequestFactory? {
  231. val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
  232. authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
  233. return authenticationRequestFactory
  234. }
  235. ----
  236. ====