19
19
import java .io .IOException ;
20
20
import java .util .Collection ;
21
21
import java .util .LinkedHashMap ;
22
+ import java .util .List ;
22
23
import java .util .Map ;
23
- import java .util .function .Function ;
24
- import java .util .stream .Collectors ;
25
24
26
25
import jakarta .servlet .ServletException ;
27
26
import jakarta .servlet .http .HttpServletRequest ;
35
34
import org .springframework .security .config .Customizer ;
36
35
import org .springframework .security .config .annotation .web .HttpSecurityBuilder ;
37
36
import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
38
- import org .springframework .security .core .Authentication ;
39
37
import org .springframework .security .core .AuthenticationException ;
40
38
import org .springframework .security .core .GrantedAuthority ;
41
- import org .springframework .security .core .context .SecurityContextHolder ;
42
- import org .springframework .security .core .context .SecurityContextHolderStrategy ;
43
- import org .springframework .security .oauth2 .server .resource .web .BearerTokenAuthenticationEntryPoint ;
44
39
import org .springframework .security .web .AuthenticationEntryPoint ;
45
- import org .springframework .security .web .FormPostRedirectStrategy ;
46
- import org .springframework .security .web .RedirectStrategy ;
47
40
import org .springframework .security .web .access .AccessDeniedHandler ;
48
41
import org .springframework .security .web .access .AccessDeniedHandlerImpl ;
49
42
import org .springframework .security .web .access .ExceptionTranslationFilter ;
50
43
import org .springframework .security .web .access .RequestMatcherDelegatingAccessDeniedHandler ;
51
44
import org .springframework .security .web .authentication .DelegatingAuthenticationEntryPoint ;
52
45
import org .springframework .security .web .authentication .Http403ForbiddenEntryPoint ;
53
- import org .springframework .security .web .authentication .LoginUrlAuthenticationEntryPoint ;
54
- import org .springframework .security .web .authentication .ott .GenerateOneTimeTokenFilter ;
55
- import org .springframework .security .web .csrf .CsrfToken ;
56
46
import org .springframework .security .web .savedrequest .HttpSessionRequestCache ;
47
+ import org .springframework .security .web .savedrequest .NullRequestCache ;
57
48
import org .springframework .security .web .savedrequest .RequestCache ;
49
+ import org .springframework .security .web .util .ThrowableAnalyzer ;
50
+ import org .springframework .security .web .util .matcher .AnyRequestMatcher ;
58
51
import org .springframework .security .web .util .matcher .RequestMatcher ;
59
52
import org .springframework .util .Assert ;
60
- import org .springframework .web .util .UriComponentsBuilder ;
61
53
62
54
/**
63
55
* Adds exception handling for Spring Security related exceptions to an application. All
@@ -102,6 +94,8 @@ public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
102
94
103
95
private LinkedHashMap <RequestMatcher , AccessDeniedHandler > defaultDeniedHandlerMappings = new LinkedHashMap <>();
104
96
97
+ private Map <String , LinkedHashMap <RequestMatcher , AuthenticationEntryPoint >> entryPoints = new LinkedHashMap <>();
98
+
105
99
/**
106
100
* Creates a new instance
107
101
* @see HttpSecurity#exceptionHandling(Customizer)
@@ -195,6 +189,26 @@ public ExceptionHandlingConfigurer<H> defaultAuthenticationEntryPointFor(Authent
195
189
return this ;
196
190
}
197
191
192
+ public ExceptionHandlingConfigurer <H > defaultAuthenticationEntryPointFor (AuthenticationEntryPoint entryPoint ,
193
+ RequestMatcher preferredMatcher , String authority ) {
194
+ this .defaultEntryPointMappings .put (preferredMatcher , entryPoint );
195
+ LinkedHashMap <RequestMatcher , AuthenticationEntryPoint > byMatcher = this .entryPoints .get (authority );
196
+ if (byMatcher == null ) {
197
+ byMatcher = new LinkedHashMap <>();
198
+ }
199
+ byMatcher .put (preferredMatcher , entryPoint );
200
+ this .entryPoints .put (authority , byMatcher );
201
+ return this ;
202
+ }
203
+
204
+ public ExceptionHandlingConfigurer <H > defaultAuthenticationEntryPointFor (AuthenticationEntryPoint entryPoint ,
205
+ String authority ) {
206
+ LinkedHashMap <RequestMatcher , AuthenticationEntryPoint > byMatcher = new LinkedHashMap <>();
207
+ byMatcher .put (AnyRequestMatcher .INSTANCE , entryPoint );
208
+ this .entryPoints .put (authority , byMatcher );
209
+ return this ;
210
+ }
211
+
198
212
/**
199
213
* Gets any explicitly configured {@link AuthenticationEntryPoint}
200
214
* @return
@@ -254,21 +268,60 @@ AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
254
268
}
255
269
256
270
private AccessDeniedHandler createDefaultDeniedHandler (H http ) {
271
+ AccessDeniedHandler defaults = createDefaultAccessDeniedHandler (http );
272
+ if (this .entryPoints .isEmpty ()) {
273
+ return defaults ;
274
+ }
275
+ Map <String , AccessDeniedHandler > deniedHandlers = new LinkedHashMap <>();
276
+ for (Map .Entry <String , LinkedHashMap <RequestMatcher , AuthenticationEntryPoint >> entry : this .entryPoints
277
+ .entrySet ()) {
278
+ AuthenticationEntryPoint entryPoint = entryPointFrom (entry .getValue ());
279
+ AuthenticationEntryPointAccessDeniedHandlerAdapter deniedHandler = new AuthenticationEntryPointAccessDeniedHandlerAdapter (
280
+ entryPoint );
281
+ RequestCache requestCache = http .getSharedObject (RequestCache .class );
282
+ if (requestCache != null ) {
283
+ deniedHandler .setRequestCache (requestCache );
284
+ }
285
+ deniedHandlers .put (entry .getKey (), deniedHandler );
286
+ }
287
+ return new AuthenticationFactorDelegatingAccessDeniedHandler (deniedHandlers , defaults );
288
+ }
289
+
290
+ private AccessDeniedHandler createDefaultAccessDeniedHandler (H http ) {
257
291
if (this .defaultDeniedHandlerMappings .isEmpty ()) {
258
- return new AuthenticationFactorDelegatingAccessDeniedHandler ();
292
+ return new AccessDeniedHandlerImpl ();
259
293
}
260
294
if (this .defaultDeniedHandlerMappings .size () == 1 ) {
261
295
return this .defaultDeniedHandlerMappings .values ().iterator ().next ();
262
296
}
263
297
return new RequestMatcherDelegatingAccessDeniedHandler (this .defaultDeniedHandlerMappings ,
264
- new AuthenticationFactorDelegatingAccessDeniedHandler ());
298
+ new AccessDeniedHandlerImpl ());
265
299
}
266
300
267
301
private AuthenticationEntryPoint createDefaultEntryPoint (H http ) {
268
- if (this .defaultEntryPoint == null ) {
302
+ AuthenticationEntryPoint defaults = entryPointFrom (this .defaultEntryPointMappings );
303
+ if (this .entryPoints .isEmpty ()) {
304
+ return defaults ;
305
+ }
306
+ Map <String , AuthenticationEntryPoint > entryPoints = new LinkedHashMap <>();
307
+ for (Map .Entry <String , LinkedHashMap <RequestMatcher , AuthenticationEntryPoint >> entry : this .entryPoints
308
+ .entrySet ()) {
309
+ entryPoints .put (entry .getKey (), entryPointFrom (entry .getValue ()));
310
+ }
311
+ return new AuthenticationFactorDelegatingAuthenticationEntryPoint (entryPoints , defaults );
312
+ }
313
+
314
+ private AuthenticationEntryPoint entryPointFrom (
315
+ LinkedHashMap <RequestMatcher , AuthenticationEntryPoint > entryPoints ) {
316
+ if (entryPoints .isEmpty ()) {
269
317
return new Http403ForbiddenEntryPoint ();
270
318
}
271
- return this .defaultEntryPoint .build ();
319
+ if (entryPoints .size () == 1 ) {
320
+ return entryPoints .values ().iterator ().next ();
321
+ }
322
+ DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint (entryPoints );
323
+ entryPoint .setDefaultEntryPoint (entryPoints .values ().iterator ().next ());
324
+ return entryPoint ;
272
325
}
273
326
274
327
/**
@@ -287,94 +340,126 @@ private RequestCache getRequestCache(H http) {
287
340
return new HttpSessionRequestCache ();
288
341
}
289
342
290
- private static final class AuthenticationFactorDelegatingAccessDeniedHandler implements AccessDeniedHandler {
343
+ private static final class AuthenticationFactorDelegatingAuthenticationEntryPoint
344
+ implements AuthenticationEntryPoint {
345
+
346
+ private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer ();
291
347
292
- private final Map <String , AuthenticationEntryPoint > entryPoints = Map .of ("FACTOR_PASSWORD" ,
293
- new LoginUrlAuthenticationEntryPoint ("/login" ), "FACTOR_AUTHORIZATION_CODE" ,
294
- new LoginUrlAuthenticationEntryPoint ("/login" ), "FACTOR_SAML_RESPONSE" ,
295
- new LoginUrlAuthenticationEntryPoint ("/login" ), "FACTOR_WEBAUTHN" ,
296
- new LoginUrlAuthenticationEntryPoint ("/login" ), "FACTOR_BEARER" ,
297
- new BearerTokenAuthenticationEntryPoint (), "FACTOR_OTT" ,
298
- new PostAuthenticationEntryPoint (GenerateOneTimeTokenFilter .DEFAULT_GENERATE_URL + "?username={u}" ,
299
- Map .of ("u" , Authentication ::getName )));
348
+ private final Map <String , AuthenticationEntryPoint > entryPoints ;
300
349
301
- private final AccessDeniedHandler defaults = new AccessDeniedHandlerImpl ();
350
+ private final AuthenticationEntryPoint defaults ;
351
+
352
+ private AuthenticationFactorDelegatingAuthenticationEntryPoint (
353
+ Map <String , AuthenticationEntryPoint > entryPoints , AuthenticationEntryPoint defaults ) {
354
+ this .entryPoints = new LinkedHashMap <>(entryPoints );
355
+ this .defaults = defaults ;
356
+ }
302
357
303
358
@ Override
304
- public void handle (HttpServletRequest request , HttpServletResponse response , AccessDeniedException ex )
359
+ public void commence (HttpServletRequest request , HttpServletResponse response , AuthenticationException ex )
305
360
throws IOException , ServletException {
306
- Collection <String > needed = authorizationRequest (ex );
307
- if (needed == null ) {
308
- this .defaults .handle (request , response , ex );
309
- return ;
361
+ Collection <GrantedAuthority > authorization = authorizationRequest (ex );
362
+ entryPoint (authorization ).commence (request , response , ex );
363
+ }
364
+
365
+ private AuthenticationEntryPoint entryPoint (Collection <GrantedAuthority > authorities ) {
366
+ if (authorities == null ) {
367
+ return this .defaults ;
310
368
}
311
- for (String authority : needed ) {
312
- AuthenticationEntryPoint entryPoint = this .entryPoints .get (authority );
369
+ for (GrantedAuthority needed : authorities ) {
370
+ AuthenticationEntryPoint entryPoint = this .entryPoints .get (needed . getAuthority () );
313
371
if (entryPoint != null ) {
314
- AuthenticationException insufficient = new InsufficientAuthenticationException (ex .getMessage (), ex );
315
- entryPoint .commence (request , response , insufficient );
316
- return ;
372
+ return entryPoint ;
317
373
}
318
374
}
319
- this .defaults . handle ( request , response , ex ) ;
375
+ return this .defaults ;
320
376
}
321
377
322
- private Collection <String > authorizationRequest (AccessDeniedException access ) {
323
- if (!(access instanceof AuthorizationDeniedException denied )) {
324
- return null ;
378
+ private Collection <GrantedAuthority > authorizationRequest (Exception ex ) {
379
+ Throwable [] chain = this .throwableAnalyzer .determineCauseChain (ex );
380
+ AuthorizationDeniedException denied = (AuthorizationDeniedException ) this .throwableAnalyzer
381
+ .getFirstThrowableOfType (AuthorizationDeniedException .class , chain );
382
+ if (denied == null ) {
383
+ return List .of ();
325
384
}
326
- if (!(denied .getAuthorizationResult () instanceof AuthorityAuthorizationDecision decision )) {
327
- return null ;
385
+ if (!(denied .getAuthorizationResult () instanceof AuthorityAuthorizationDecision authorization )) {
386
+ return List . of () ;
328
387
}
329
- return decision .getAuthorities (). stream (). map ( GrantedAuthority :: getAuthority ). toList ();
388
+ return authorization .getAuthorities ();
330
389
}
331
390
332
391
}
333
392
334
- private static final class PostAuthenticationEntryPoint implements AuthenticationEntryPoint {
393
+ private static final class AuthenticationEntryPointAccessDeniedHandlerAdapter implements AccessDeniedHandler {
394
+
395
+ private final AuthenticationEntryPoint entryPoint ;
396
+
397
+ private RequestCache requestCache = new NullRequestCache ();
398
+
399
+ private AuthenticationEntryPointAccessDeniedHandlerAdapter (AuthenticationEntryPoint entryPoint ) {
400
+ this .entryPoint = entryPoint ;
401
+ }
402
+
403
+ void setRequestCache (RequestCache requestCache ) {
404
+ Assert .notNull (requestCache , "requestCache cannot be null" );
405
+ this .requestCache = requestCache ;
406
+ }
407
+
408
+ @ Override
409
+ public void handle (HttpServletRequest request , HttpServletResponse response , AccessDeniedException denied )
410
+ throws IOException , ServletException {
411
+ AuthenticationException ex = new InsufficientAuthenticationException ("access denied" , denied );
412
+ this .requestCache .saveRequest (request , response );
413
+ this .entryPoint .commence (request , response , ex );
414
+ }
335
415
336
- private final String entryPointUri ;
416
+ }
417
+
418
+ private static final class AuthenticationFactorDelegatingAccessDeniedHandler implements AccessDeniedHandler {
337
419
338
- private final Map < String , Function < Authentication , String >> params ;
420
+ private final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer () ;
339
421
340
- private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
341
- .getContextHolderStrategy ();
422
+ private final Map <String , AccessDeniedHandler > deniedHandlers ;
342
423
343
- private RedirectStrategy redirectStrategy = new FormPostRedirectStrategy () ;
424
+ private final AccessDeniedHandler defaults ;
344
425
345
- private PostAuthenticationEntryPoint ( String entryPointUri ,
346
- Map < String , Function < Authentication , String >> params ) {
347
- this .entryPointUri = entryPointUri ;
348
- this .params = params ;
426
+ private AuthenticationFactorDelegatingAccessDeniedHandler ( Map < String , AccessDeniedHandler > deniedHandlers ,
427
+ AccessDeniedHandler defaults ) {
428
+ this .deniedHandlers = new LinkedHashMap <>( deniedHandlers ) ;
429
+ this .defaults = defaults ;
349
430
}
350
431
351
432
@ Override
352
- public void commence (HttpServletRequest request , HttpServletResponse response ,
353
- AuthenticationException authException ) throws IOException , ServletException {
354
- Authentication authentication = getAuthentication (authException );
355
- Assert .notNull (authentication , "could not find authentication in order to perform post" );
356
- Map <String , String > params = this .params .entrySet ()
357
- .stream ()
358
- .collect (Collectors .toMap (Map .Entry ::getKey , (entry ) -> entry .getValue ().apply (authentication )));
359
- UriComponentsBuilder builder = UriComponentsBuilder .fromUriString (this .entryPointUri );
360
- CsrfToken csrf = (CsrfToken ) request .getAttribute (CsrfToken .class .getName ());
361
- if (csrf != null ) {
362
- builder .queryParam (csrf .getParameterName (), csrf .getToken ());
433
+ public void handle (HttpServletRequest request , HttpServletResponse response , AccessDeniedException ex )
434
+ throws IOException , ServletException {
435
+ Collection <GrantedAuthority > authorization = authorizationRequest (ex );
436
+ deniedHandler (authorization ).handle (request , response , ex );
437
+ }
438
+
439
+ private AccessDeniedHandler deniedHandler (Collection <GrantedAuthority > authorities ) {
440
+ if (authorities == null ) {
441
+ return this .defaults ;
442
+ }
443
+ for (GrantedAuthority needed : authorities ) {
444
+ AccessDeniedHandler deniedHandler = this .deniedHandlers .get (needed .getAuthority ());
445
+ if (deniedHandler != null ) {
446
+ return deniedHandler ;
447
+ }
363
448
}
364
- String entryPointUrl = builder .build (false ).expand (params ).toUriString ();
365
- this .redirectStrategy .sendRedirect (request , response , entryPointUrl );
449
+ return this .defaults ;
366
450
}
367
451
368
- private Authentication getAuthentication (AuthenticationException authException ) {
369
- Authentication authentication = authException .getAuthenticationRequest ();
370
- if (authentication != null && authentication .isAuthenticated ()) {
371
- return authentication ;
452
+ private Collection <GrantedAuthority > authorizationRequest (Exception ex ) {
453
+ Throwable [] chain = this .throwableAnalyzer .determineCauseChain (ex );
454
+ AuthorizationDeniedException denied = (AuthorizationDeniedException ) this .throwableAnalyzer
455
+ .getFirstThrowableOfType (AuthorizationDeniedException .class , chain );
456
+ if (denied == null ) {
457
+ return List .of ();
372
458
}
373
- authentication = this .securityContextHolderStrategy .getContext ().getAuthentication ();
374
- if (authentication != null && authentication .isAuthenticated ()) {
375
- return authentication ;
459
+ if (!(denied .getAuthorizationResult () instanceof AuthorityAuthorizationDecision authorization )) {
460
+ return List .of ();
376
461
}
377
- return null ;
462
+ return authorization . getAuthorities () ;
378
463
}
379
464
380
465
}
0 commit comments