2
0

saml2.adoc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. = SAML Migrations
  2. The following steps relate to changes around how to configure SAML 2.0.
  3. == Use OpenSAML 4
  4. OpenSAML 3 has reached its end-of-life.
  5. As such, Spring Security 6 drops support for it, bumping up its OpenSAML baseline to 4.
  6. To prepare for the upgrade, update your pom to depend on OpenSAML 4 instead of 3:
  7. [tabs]
  8. ======
  9. Maven::
  10. +
  11. [source,maven,role="primary"]
  12. ----
  13. <dependencyManagement>
  14. <dependency>
  15. <groupId>org.opensaml</groupId>
  16. <artifactId>opensaml-core</artifactId>
  17. <version>4.2.1</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.opensaml</groupId>
  21. <artifactId>opensaml-saml-api</artifactId>
  22. <version>4.2.1</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.opensaml</groupId>
  26. <artifactId>opensaml-saml-impl</artifactId>
  27. <version>4.2.1</version>
  28. </dependency>
  29. </dependencyManagement>
  30. ----
  31. Gradle::
  32. +
  33. [source,gradle,role="secondary"]
  34. ----
  35. dependencies {
  36. constraints {
  37. api "org.opensaml:opensaml-core:4.2.1"
  38. api "org.opensaml:opensaml-saml-api:4.2.1"
  39. api "org.opensaml:opensaml-saml-impl:4.2.1"
  40. }
  41. }
  42. ----
  43. ======
  44. You must use at least OpenSAML 4.1.1 to update to Spring Security 6's SAML support.
  45. == Use `OpenSaml4AuthenticationProvider`
  46. In order to support both OpenSAML 3 and 4 at the same time, Spring Security released `OpenSamlAuthenticationProvider` and `OpenSaml4AuthenticationProvider`.
  47. In 6.0, because OpenSAML3 support is removed, `OpenSamlAuthenticationProvider` is removed as well.
  48. Not all methods in `OpenSamlAuthenticationProvider` were ported 1-to-1 to `OpenSaml4AuthenticationProvider`.
  49. As such, some adjustment will be required to make the challenge.
  50. Consider the following representative usage of `OpenSamlAuthenticationProvider`:
  51. [tabs]
  52. ======
  53. Java::
  54. +
  55. [source,java,role="primary"]
  56. ----
  57. OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider();
  58. versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor);
  59. versionThree.setResponseTimeValidationSkew(myDuration);
  60. ----
  61. Kotlin::
  62. +
  63. [source,kotlin,role="secondary"]
  64. ----
  65. val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider()
  66. versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor)
  67. versionThree.setResponseTimeValidationSkew(myDuration)
  68. ----
  69. ======
  70. This should change to:
  71. [tabs]
  72. ======
  73. Java::
  74. +
  75. [source,java,role="primary"]
  76. ----
  77. Converter<ResponseToken, Saml2Authentication> delegate = OpenSaml4AuthenticationProvider
  78. .createDefaultResponseAuthenticationConverter();
  79. OpenSaml4AuthenticationProvider versionFour = new OpenSaml4AuthenticationProvider();
  80. versionFour.setResponseAuthenticationConverter((responseToken) -> {
  81. Saml2Authentication authentication = delegate.convert(responseToken);
  82. Assertion assertion = responseToken.getResponse().getAssertions().get(0);
  83. AuthenticatedPrincipal principal = (AuthenticatedPrincipal) authentication.getPrincipal();
  84. Collection<GrantedAuthority> authorities = myAuthoritiesExtractor.convert(assertion);
  85. return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities);
  86. });
  87. Converter<AssertionToken, Saml2ResponseValidationResult> validator = OpenSaml4AuthenticationProvider
  88. .createDefaultAssertionValidatorWithParameters((p) -> p.put(CLOCK_SKEW, myDuration));
  89. versionFour.setAssertionValidator(validator);
  90. ----
  91. Kotlin::
  92. +
  93. [source,kotlin,role="secondary"]
  94. ----
  95. val delegate = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter()
  96. val versionFour = OpenSaml4AuthenticationProvider()
  97. versionFour.setResponseAuthenticationConverter({
  98. responseToken -> {
  99. val authentication = delegate.convert(responseToken)
  100. val assertion = responseToken.getResponse().getAssertions().get(0)
  101. val principal = (AuthenticatedPrincipal) authentication.getPrincipal()
  102. val authorities = myAuthoritiesExtractor.convert(assertion)
  103. return Saml2Authentication(principal, authentication.getSaml2Response(), authorities)
  104. }
  105. })
  106. val validator = OpenSaml4AuthenticationProvider
  107. .createDefaultAssertionValidatorWithParameters({ p -> p.put(CLOCK_SKEW, myDuration) })
  108. versionFour.setAssertionValidator(validator)
  109. ----
  110. ======
  111. == Stop Using SAML 2.0 `Converter` constructors
  112. In an early release of Spring Security's SAML 2.0 support, `Saml2MetadataFilter` and `Saml2AuthenticationTokenConverter` shipped with constructors of type `Converter`.
  113. This level of abstraction made it tricky to evolve the class and so a dedicated interface `RelyingPartyRegistrationResolver` was introduced in a later release.
  114. In 6.0, the `Converter` constructors are removed.
  115. To prepare for this in 5.8, change classes that implement `Converter<HttpServletRequest, RelyingPartyRegistration>` to instead implement `RelyingPartyRegistrationResolver`.
  116. == Change to Using `Saml2AuthenticationRequestResolver`
  117. `Saml2AuthenticationContextResolver` and `Saml2AuthenticationRequestFactory` are removed in 6.0 as is the `Saml2WebSsoAuthenticationRequestFilter` that requires them.
  118. They are replaced by `Saml2AuthenticationRequestResolver` and a new constructor in `Saml2WebSsoAuthenticationRequestFilter`.
  119. The new interface removes an unnecessary transport object between the two classes.
  120. Most applications need do nothing; however, if you use or configure `Saml2AuthenticationRequestContextResolver` or `Saml2AuthenticationRequestFactory`, try the following steps to convert instead use `Saml2AuthenticationRequestResolver`.
  121. === Use `setAuthnRequestCustomizer` instead of `setAuthenticationRequestContextConverter`
  122. If you are calling `OpenSaml4AuthenticationReqeustFactory#setAuthenticationRequestContextConverter`, for example, like so:
  123. [tabs]
  124. ======
  125. Java::
  126. +
  127. [source,java,role="primary"]
  128. ----
  129. @Bean
  130. Saml2AuthenticationRequestFactory authenticationRequestFactory() {
  131. OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
  132. factory.setAuthenticationRequestContextConverter((context) -> {
  133. AuthnRequestBuilder authnRequestBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
  134. .getBuilderFactory().getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
  135. IssuerBuilder issuerBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
  136. .getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
  137. tring issuer = context.getIssuer();
  138. String destination = context.getDestination();
  139. String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
  140. String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
  141. AuthnRequest auth = authnRequestBuilder.buildObject();
  142. auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
  143. auth.setIssueInstant(Instant.now());
  144. auth.setForceAuthn(Boolean.TRUE);
  145. auth.setIsPassive(Boolean.FALSE);
  146. auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
  147. Issuer iss = issuerBuilder.buildObject();
  148. iss.setValue(issuer);
  149. auth.setIssuer(iss);
  150. auth.setDestination(destination);
  151. auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
  152. });
  153. return factory;
  154. }
  155. ----
  156. ======
  157. to ensure that ForceAuthn is set to `true`, you can instead do:
  158. [tabs]
  159. ======
  160. Java::
  161. +
  162. [source,java,role="primary"]
  163. ----
  164. @Bean
  165. Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationResolver registrations) {
  166. OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
  167. resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest().setForceAuthn(Boolean.TRUE));
  168. return resolver;
  169. }
  170. ----
  171. ======
  172. Also, since `setAuthnRequestCustomizer` has direct access to the `HttpServletRequest`, there is no need for a `Saml2AuthenticationRequestContextResolver`.
  173. Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest` this information you need.
  174. === Use `setAuthnRequestCustomizer` instead of `setProtocolBinding`
  175. Instead of doing:
  176. [tabs]
  177. ======
  178. Java::
  179. +
  180. [source,java,role="primary"]
  181. ----
  182. @Bean
  183. Saml2AuthenticationRequestFactory authenticationRequestFactory() {
  184. OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
  185. factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
  186. return factory;
  187. }
  188. ----
  189. ======
  190. you can do:
  191. [tabs]
  192. ======
  193. Java::
  194. +
  195. [source,java,role="primary"]
  196. ----
  197. @Bean
  198. Saml2AuthenticationRequestResolver authenticationRequestResolver() {
  199. OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
  200. resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest()
  201. .setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"));
  202. return resolver;
  203. }
  204. ----
  205. ======
  206. [NOTE]
  207. ====
  208. Since Spring Security only supports the `POST` binding for authentication, there is not very much value in overriding the protocol binding at this point in time.
  209. ====
  210. == Use the latest `Saml2AuthenticationToken` constructor
  211. In an early release, `Saml2AuthenticationToken` took several individual settings as constructor parameters.
  212. This created a challenge each time a new parameter needed to be added.
  213. Since most of these settings were part of `RelyingPartyRegistration`, a new constructor was added where a `RelyingPartyRegistration` could be provided, making the constructor more stable.
  214. It also is valuable in that it more closely aligns with the design of `OAuth2LoginAuthenticationToken`.
  215. Most applications do not construct this class directly since `Saml2WebSsoAuthenticationFilter` does.
  216. However, in the event that your application constructs one, please change from:
  217. [tabs]
  218. ======
  219. Java::
  220. +
  221. [source,java,role="primary"]
  222. ----
  223. new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
  224. registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
  225. ----
  226. Kotlin::
  227. +
  228. [source,kotlin,role="secondary"]
  229. ----
  230. Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
  231. registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
  232. ----
  233. ======
  234. to:
  235. [tabs]
  236. ======
  237. Java::
  238. +
  239. [source,java,role="primary"]
  240. ----
  241. new Saml2AuthenticationToken(saml2Response, registration)
  242. ----
  243. Kotlin::
  244. +
  245. [source,kotlin,role="secondary"]
  246. ----
  247. Saml2AuthenticationToken(saml2Response, registration)
  248. ----
  249. ======
  250. == Use `RelyingPartyRegistration` updated methods
  251. In an early release of Spring Security's SAML support, there was some ambiguity on the meaning of certain `RelyingPartyRegistration` methods and their function.
  252. As more capabilities were added to `RelyingPartyRegistration`, it became necessary to clarify this ambiguity by changing method names to ones that aligned with spec language.
  253. The deprecated methods in `RelyingPartyRegstration` are removed.
  254. To prepare for that, consider the following representative usage of `RelyingPartyRegistration`:
  255. [tabs]
  256. ======
  257. Java::
  258. +
  259. [source,java,role="primary"]
  260. ----
  261. String idpEntityId = registration.getRemoteIdpEntityId();
  262. String assertionConsumerServiceUrl = registration.getAssertionConsumerServiceUrlTemplate();
  263. String idpWebSsoUrl = registration.getIdpWebSsoUrl();
  264. String localEntityId = registration.getLocalEntityIdTemplate();
  265. List<Saml2X509Credential> verifying = registration.getCredentials().stream()
  266. .filter(Saml2X509Credential::isSignatureVerficationCredential)
  267. .collect(Collectors.toList());
  268. ----
  269. Kotlin::
  270. +
  271. [source,kotlin,role="secondary"]
  272. ----
  273. val idpEntityId: String = registration.getRemoteIdpEntityId()
  274. val assertionConsumerServiceUrl: String = registration.getAssertionConsumerServiceUrlTemplate()
  275. val idpWebSsoUrl: String = registration.getIdpWebSsoUrl()
  276. val localEntityId: String = registration.getLocalEntityIdTemplate()
  277. val verifying: List<Saml2X509Credential> = registration.getCredentials()
  278. .filter(Saml2X509Credential::isSignatureVerficationCredential)
  279. ----
  280. ======
  281. This should change to:
  282. [tabs]
  283. ======
  284. Java::
  285. +
  286. [source,java,role="primary"]
  287. ----
  288. String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
  289. String assertionConsumerServiceLocation = registration.getAssertionConsumerServiceLocation();
  290. String singleSignOnServiceLocation = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
  291. String entityId = registration.getEntityId();
  292. List<Saml2X509Credential> verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials();
  293. ----
  294. Kotlin::
  295. +
  296. [source,kotlin,role="secondary"]
  297. ----
  298. val assertingPartyEntityId: String = registration.getAssertingPartyDetails().getEntityId()
  299. val assertionConsumerServiceLocation: String = registration.getAssertionConsumerServiceLocation()
  300. val singleSignOnServiceLocation: String = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()
  301. val entityId: String = registration.getEntityId()
  302. val verifying: List<Saml2X509Credential> = registration.getAssertingPartyDetails().getVerificationX509Credentials()
  303. ----
  304. ======
  305. For a complete listing of all changed methods, please see {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[``RelyingPartyRegistration``'s JavaDoc].