authentication-requests.adoc 11 KB

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