2
0

saml2.adoc 13 KB

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