|
1 | 1 | = Saml 2.0 Migrations
|
2 | 2 |
|
| 3 | +== Use OpenSAML 5 By Default |
| 4 | + |
| 5 | +OpenSAML 4.x is no longer supported by the OpenSAML team. |
| 6 | +As such, Spring Security will default to using its `OpenSaml5` components in all cases. |
| 7 | + |
| 8 | +If you want to see how well your application will respond to this, do the following: |
| 9 | + |
| 10 | +1. Update your OpenSAML dependencies to 5.x |
| 11 | +2. If you are constructing an `OpenSaml4XXX` Spring Security component, change it to `OpenSaml5`. |
| 12 | + |
| 13 | +If you cannot opt-in, then add the `opensaml-saml-api` and `opensaml-saml-impl` 4.x dependencies and exclude the 5.x dependencies from `spring-security-saml2-service-provider`. |
| 14 | + |
3 | 15 | == Continue Filter Chain When No Relying Party Found
|
4 | 16 |
|
5 | 17 | In Spring Security 6, `Saml2WebSsoAuthenticationFilter` throws an exception when the request URI matches, but no relying party registration is found.
|
@@ -163,3 +175,230 @@ val responseValidator = ResponseValidator.withDefaults { responseToken: Response
|
163 | 175 | provider.setResponseValidator(responseValidator)
|
164 | 176 | ----
|
165 | 177 | ======
|
| 178 | + |
| 179 | +== `RelyingPartyRegistration` Improvements |
| 180 | + |
| 181 | +`RelyingPartyRegistration` links metadata from a relying party to metadata from an asserting party. |
| 182 | + |
| 183 | +To prepare for some improvements to the API, please take the following steps: |
| 184 | + |
| 185 | +1. If you are mutating a registration by using `RelyingPartyRegistration#withRelyingPartyRegistration`, instead call `RelyingPartyRegistration#mutate` |
| 186 | +2. If you are providing or retrieving `AssertingPartyDetails`, use `getAssertingPartyMetadata` or `withAssertingPartyMetadata` instead. |
| 187 | + |
| 188 | +== `OpenSaml5AuthenticationProvider` Improvements |
| 189 | + |
| 190 | +Spring Security 7 will remove a handful of static factories from `OpenSaml5AuthenticationProvider` in favor of inner classes. |
| 191 | +These inner classes simplify customization of the response validator, the assertion validator, and the response authentication converter. |
| 192 | + |
| 193 | +=== Response Validation |
| 194 | + |
| 195 | +Instead of doing: |
| 196 | + |
| 197 | +[tabs] |
| 198 | +====== |
| 199 | +Java:: |
| 200 | ++ |
| 201 | +[source,java,role="primary"] |
| 202 | +---- |
| 203 | +@Bean |
| 204 | +OpenSaml5AuthenticationProvider saml2AuthenticationProvider() { |
| 205 | + OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider(); |
| 206 | + saml2.setResponseValidator((responseToken) -> OpenSamlAuthenticationProvider.createDefaultResponseValidator() |
| 207 | + .andThen((result) -> result |
| 208 | + .concat(myCustomValidator.convert(responseToken)) |
| 209 | + )); |
| 210 | + return saml2; |
| 211 | +} |
| 212 | +---- |
| 213 | +
|
| 214 | +Kotlin:: |
| 215 | ++ |
| 216 | +[source,kotlin,role="secondary"] |
| 217 | +---- |
| 218 | +@Bean |
| 219 | +fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider { |
| 220 | + val saml2 = OpenSaml5AuthenticationProvider() |
| 221 | + saml2.setResponseValidator { responseToken -> OpenSamlAuthenticationProvider.createDefaultResponseValidator() |
| 222 | + .andThen { result -> result |
| 223 | + .concat(myCustomValidator.convert(responseToken)) |
| 224 | + } |
| 225 | + } |
| 226 | + return saml2 |
| 227 | +} |
| 228 | +---- |
| 229 | +====== |
| 230 | + |
| 231 | +use `OpenSaml5AuthenticationProvider.ResponseValidator`: |
| 232 | + |
| 233 | +[tabs] |
| 234 | +====== |
| 235 | +Java:: |
| 236 | ++ |
| 237 | +[source,java,role="primary"] |
| 238 | +---- |
| 239 | +@Bean |
| 240 | +OpenSaml5AuthenticationProvider saml2AuthenticationProvider() { |
| 241 | + OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider(); |
| 242 | + saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator)); |
| 243 | + return saml2; |
| 244 | +} |
| 245 | +---- |
| 246 | +
|
| 247 | +Kotlin:: |
| 248 | ++ |
| 249 | +[source,kotlin,role="secondary"] |
| 250 | +---- |
| 251 | +@Bean |
| 252 | +fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider { |
| 253 | + val saml2 = OpenSaml5AuthenticationProvider() |
| 254 | + saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator)) |
| 255 | + return saml2 |
| 256 | +} |
| 257 | +---- |
| 258 | +====== |
| 259 | + |
| 260 | +=== Assertion Validation |
| 261 | + |
| 262 | +Instead of doing: |
| 263 | + |
| 264 | +[tabs] |
| 265 | +====== |
| 266 | +Java:: |
| 267 | ++ |
| 268 | +[source,java,role="primary"] |
| 269 | +---- |
| 270 | +@Bean |
| 271 | +OpenSaml5AuthenticationProvider saml2AuthenticationProvider() { |
| 272 | + OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider(); |
| 273 | + authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider |
| 274 | + .createDefaultAssertionValidatorWithParameters(assertionToken -> { |
| 275 | + Map<String, Object> params = new HashMap<>(); |
| 276 | + params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis()); |
| 277 | + // ... other validation parameters |
| 278 | + return new ValidationContext(params); |
| 279 | + }) |
| 280 | + ); |
| 281 | + return saml2; |
| 282 | +} |
| 283 | +---- |
| 284 | +
|
| 285 | +Kotlin:: |
| 286 | ++ |
| 287 | +[source,kotlin,role="secondary"] |
| 288 | +---- |
| 289 | +@Bean |
| 290 | +fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider { |
| 291 | + val saml2 = OpenSaml5AuthenticationProvider() |
| 292 | + authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider |
| 293 | + .createDefaultAssertionValidatorWithParameters { -> |
| 294 | + val params = HashMap<String, Object>() |
| 295 | + params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis()) |
| 296 | + // ... other validation parameters |
| 297 | + return ValidationContext(params) |
| 298 | + } |
| 299 | + ) |
| 300 | + return saml2 |
| 301 | +} |
| 302 | +---- |
| 303 | +====== |
| 304 | + |
| 305 | +use `OpenSaml5AuthenticationProvider.AssertionValidator`: |
| 306 | + |
| 307 | +[tabs] |
| 308 | +====== |
| 309 | +Java:: |
| 310 | ++ |
| 311 | +[source,java,role="primary"] |
| 312 | +---- |
| 313 | +@Bean |
| 314 | +OpenSaml5AuthenticationProvider saml2AuthenticationProvider() { |
| 315 | + OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider(); |
| 316 | + Duration tenMinutes = Duration.ofMinutes(10); |
| 317 | + authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build()); |
| 318 | + return saml2; |
| 319 | +} |
| 320 | +---- |
| 321 | +
|
| 322 | +Kotlin:: |
| 323 | ++ |
| 324 | +[source,kotlin,role="secondary"] |
| 325 | +---- |
| 326 | +@Bean |
| 327 | +fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider { |
| 328 | + val saml2 = OpenSaml5AuthenticationProvider() |
| 329 | + val tenMinutes = Duration.ofMinutes(10) |
| 330 | + authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build()) |
| 331 | + return saml2 |
| 332 | +} |
| 333 | +---- |
| 334 | +====== |
| 335 | + |
| 336 | +== Response Authentication Converter |
| 337 | + |
| 338 | +Instead of doing: |
| 339 | + |
| 340 | +[tabs] |
| 341 | +====== |
| 342 | +Java:: |
| 343 | ++ |
| 344 | +[source,java,role="primary"] |
| 345 | +---- |
| 346 | +@Bean |
| 347 | +Converter<ResponseToken, Saml2Authentication> authenticationConverter() { |
| 348 | + return (responseToken) -> { |
| 349 | + Saml2Authentication authentication = OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter() |
| 350 | + .convert(responseToken); |
| 351 | + // ... work with OpenSAML's Assertion object to extract the principal |
| 352 | + return new Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities()); |
| 353 | + }; |
| 354 | +} |
| 355 | +---- |
| 356 | +
|
| 357 | +Kotlin:: |
| 358 | ++ |
| 359 | +[source,kotlin,role="secondary"] |
| 360 | +---- |
| 361 | +@Bean |
| 362 | +fun authenticationConverter(): Converter<ResponseToken, Saml2Authentication> { |
| 363 | + return { responseToken -> |
| 364 | + val authentication = |
| 365 | + OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken) |
| 366 | + // ... work with OpenSAML's Assertion object to extract the principal |
| 367 | + return Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities()) |
| 368 | + } |
| 369 | +} |
| 370 | +---- |
| 371 | +====== |
| 372 | + |
| 373 | +use `OpenSaml5AuthenticationProvider.ResponseAuthenticationConverter`: |
| 374 | + |
| 375 | +[tabs] |
| 376 | +====== |
| 377 | +Java:: |
| 378 | ++ |
| 379 | +[source,java,role="primary"] |
| 380 | +---- |
| 381 | +@Bean |
| 382 | +ResponseAuthenticationConverter authenticationConverter() { |
| 383 | + ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter(); |
| 384 | + authenticationConverter.setPrincipalNameConverter((assertion) -> { |
| 385 | + // ... work with OpenSAML's Assertion object to extract the principal |
| 386 | + }); |
| 387 | + return authenticationConverter; |
| 388 | +} |
| 389 | +---- |
| 390 | +
|
| 391 | +Kotlin:: |
| 392 | ++ |
| 393 | +[source,kotlin,role="secondary"] |
| 394 | +---- |
| 395 | +@Bean |
| 396 | +fun authenticationConverter(): ResponseAuthenticationConverter { |
| 397 | + val authenticationConverter = ResponseAuthenticationConverter() |
| 398 | + authenticationConverter.setPrincipalNameConverter { assertion -> |
| 399 | + // ... work with OpenSAML's Assertion object to extract the principal |
| 400 | + } |
| 401 | + return authenticationConverter |
| 402 | +} |
| 403 | +---- |
| 404 | +====== |
0 commit comments