37
37
import org .opensaml .saml .saml2 .core .Assertion ;
38
38
import org .opensaml .saml .saml2 .core .AuthnRequest ;
39
39
40
+ import org .springframework .beans .factory .BeanCreationException ;
40
41
import org .springframework .beans .factory .annotation .Autowired ;
41
42
import org .springframework .context .ConfigurableApplicationContext ;
42
43
import org .springframework .context .annotation .Bean ;
49
50
import org .springframework .security .authentication .AuthenticationProvider ;
50
51
import org .springframework .security .authentication .AuthenticationServiceException ;
51
52
import org .springframework .security .authentication .ProviderManager ;
53
+ import org .springframework .security .config .Customizer ;
52
54
import org .springframework .security .config .annotation .ObjectPostProcessor ;
53
55
import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
54
56
import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
77
79
import org .springframework .security .saml2 .provider .service .registration .TestRelyingPartyRegistrations ;
78
80
import org .springframework .security .saml2 .provider .service .servlet .filter .Saml2WebSsoAuthenticationFilter ;
79
81
import org .springframework .security .saml2 .provider .service .web .Saml2AuthenticationRequestContextResolver ;
82
+ import org .springframework .security .saml2 .provider .service .web .Saml2AuthenticationTokenConverter ;
80
83
import org .springframework .security .web .FilterChainProxy ;
84
+ import org .springframework .security .web .SecurityFilterChain ;
81
85
import org .springframework .security .web .authentication .AuthenticationConverter ;
82
86
import org .springframework .security .web .authentication .AuthenticationFailureHandler ;
83
87
import org .springframework .security .web .context .HttpRequestResponseHolder ;
91
95
import org .springframework .web .util .UriComponentsBuilder ;
92
96
93
97
import static org .assertj .core .api .Assertions .assertThat ;
98
+ import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
94
99
import static org .mockito .ArgumentMatchers .any ;
95
100
import static org .mockito .ArgumentMatchers .anyString ;
96
101
import static org .mockito .BDDMockito .given ;
@@ -117,6 +122,8 @@ public class Saml2LoginConfigurerTests {
117
122
118
123
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=" ;
119
124
125
+ private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock (AuthenticationConverter .class );
126
+
120
127
@ Autowired
121
128
private ConfigurableApplicationContext context ;
122
129
@@ -230,6 +237,33 @@ public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses()
230
237
assertThat (exception .getCause ()).isInstanceOf (IOException .class );
231
238
}
232
239
240
+ @ Test
241
+ public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates () {
242
+ assertThatExceptionOfType (BeanCreationException .class )
243
+ .isThrownBy (() -> this .spring .register (CustomLoginProcessingUrlDefaultAuthenticationConverter .class )
244
+ .autowire ())
245
+ .havingRootCause ().isInstanceOf (IllegalStateException .class )
246
+ .withMessage ("loginProcessingUrl must contain {registrationId} path variable" );
247
+ }
248
+
249
+ @ Test
250
+ public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate ()
251
+ throws Exception {
252
+ this .spring .register (CustomLoginProcessingUrlCustomAuthenticationConverter .class ).autowire ();
253
+ RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations .noCredentials ()
254
+ .assertingPartyDetails ((party ) -> party .verificationX509Credentials (
255
+ (c ) -> c .add (TestSaml2X509Credentials .relyingPartyVerifyingCredential ())))
256
+ .build ();
257
+ String response = new String (Saml2Utils .samlDecode (SIGNED_RESPONSE ));
258
+ given (AUTHENTICATION_CONVERTER .convert (any (HttpServletRequest .class )))
259
+ .willReturn (new Saml2AuthenticationToken (relyingPartyRegistration , response ));
260
+ // @formatter:off
261
+ MockHttpServletRequestBuilder request = post ("/my/custom/url" ).param ("SAMLResponse" , SIGNED_RESPONSE );
262
+ // @formatter:on
263
+ this .mvc .perform (request ).andExpect (redirectedUrl ("/" ));
264
+ verify (AUTHENTICATION_CONVERTER ).convert (any (HttpServletRequest .class ));
265
+ }
266
+
233
267
private void validateSaml2WebSsoAuthenticationFilterConfiguration () {
234
268
// get the OpenSamlAuthenticationProvider
235
269
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter (this .springSecurityFilterChain );
@@ -337,10 +371,10 @@ static class CustomAuthenticationRequestContextResolver extends WebSecurityConfi
337
371
protected void configure (HttpSecurity http ) throws Exception {
338
372
// @formatter:off
339
373
http
340
- .authorizeRequests ((authz ) -> authz
341
- .anyRequest ().authenticated ()
342
- )
343
- .saml2Login (withDefaults ());
374
+ .authorizeRequests ((authz ) -> authz
375
+ .anyRequest ().authenticated ()
376
+ )
377
+ .saml2Login (withDefaults ());
344
378
// @formatter:on
345
379
}
346
380
@@ -359,11 +393,11 @@ static class CustomAuthenticationRequestContextConverterResolver extends WebSecu
359
393
protected void configure (HttpSecurity http ) throws Exception {
360
394
// @formatter:off
361
395
http
362
- .authorizeRequests ((authz ) -> authz
363
- .anyRequest ().authenticated ()
364
- )
365
- .saml2Login ((saml2 ) -> {
366
- });
396
+ .authorizeRequests ((authz ) -> authz
397
+ .anyRequest ().authenticated ()
398
+ )
399
+ .saml2Login ((saml2 ) -> {
400
+ });
367
401
// @formatter:on
368
402
}
369
403
@@ -395,6 +429,62 @@ protected void configure(HttpSecurity http) throws Exception {
395
429
396
430
}
397
431
432
+ @ EnableWebSecurity
433
+ @ Import (Saml2LoginConfigBeans .class )
434
+ static class CustomAuthenticationConverterBean {
435
+
436
+ private final Saml2AuthenticationTokenConverter authenticationConverter = mock (
437
+ Saml2AuthenticationTokenConverter .class );
438
+
439
+ @ Bean
440
+ SecurityFilterChain app (HttpSecurity http ) throws Exception {
441
+ http .authorizeHttpRequests ((authz ) -> authz .anyRequest ().authenticated ())
442
+ .saml2Login (Customizer .withDefaults ());
443
+ return http .build ();
444
+ }
445
+
446
+ @ Bean
447
+ Saml2AuthenticationTokenConverter authenticationConverter () {
448
+ return this .authenticationConverter ;
449
+ }
450
+
451
+ }
452
+
453
+ @ EnableWebSecurity
454
+ @ Import (Saml2LoginConfigBeans .class )
455
+ static class CustomLoginProcessingUrlDefaultAuthenticationConverter {
456
+
457
+ @ Bean
458
+ SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
459
+ // @formatter:off
460
+ http
461
+ .authorizeRequests ((authz ) -> authz .anyRequest ().authenticated ())
462
+ .saml2Login ((saml2 ) -> saml2 .loginProcessingUrl ("/my/custom/url" ));
463
+ // @formatter:on
464
+ return http .build ();
465
+ }
466
+
467
+ }
468
+
469
+ @ EnableWebSecurity
470
+ @ Import (Saml2LoginConfigBeans .class )
471
+ static class CustomLoginProcessingUrlCustomAuthenticationConverter {
472
+
473
+ @ Bean
474
+ SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
475
+ // @formatter:off
476
+ http
477
+ .authorizeRequests ((authz ) -> authz .anyRequest ().authenticated ())
478
+ .saml2Login ((saml2 ) -> saml2
479
+ .loginProcessingUrl ("/my/custom/url" )
480
+ .authenticationConverter (AUTHENTICATION_CONVERTER )
481
+ );
482
+ // @formatter:on
483
+ return http .build ();
484
+ }
485
+
486
+ }
487
+
398
488
static class Saml2LoginConfigBeans {
399
489
400
490
@ Bean
0 commit comments