@@ -475,69 +475,6 @@ private static boolean requireAuthorizationConsent(RegisteredClient registeredCl
475
475
return true ;
476
476
}
477
477
478
- private static boolean isValidRedirectUri (String requestedRedirectUri , RegisteredClient registeredClient ) {
479
- UriComponents requestedRedirect ;
480
- try {
481
- requestedRedirect = UriComponentsBuilder .fromUriString (requestedRedirectUri ).build ();
482
- if (requestedRedirect .getFragment () != null ) {
483
- return false ;
484
- }
485
- } catch (Exception ex ) {
486
- return false ;
487
- }
488
-
489
- String requestedRedirectHost = requestedRedirect .getHost ();
490
- if (requestedRedirectHost == null || requestedRedirectHost .equals ("localhost" )) {
491
- // As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7.1
492
- // While redirect URIs using localhost (i.e.,
493
- // "http://localhost:{port}/{path}") function similarly to loopback IP
494
- // redirects described in Section 10.3.3, the use of "localhost" is NOT RECOMMENDED.
495
- return false ;
496
- }
497
- if (!isLoopbackAddress (requestedRedirectHost )) {
498
- // As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7
499
- // When comparing client redirect URIs against pre-registered URIs,
500
- // authorization servers MUST utilize exact string matching.
501
- return registeredClient .getRedirectUris ().contains (requestedRedirectUri );
502
- }
503
-
504
- // As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-10.3.3
505
- // The authorization server MUST allow any port to be specified at the
506
- // time of the request for loopback IP redirect URIs, to accommodate
507
- // clients that obtain an available ephemeral port from the operating
508
- // system at the time of the request.
509
- for (String registeredRedirectUri : registeredClient .getRedirectUris ()) {
510
- UriComponentsBuilder registeredRedirect = UriComponentsBuilder .fromUriString (registeredRedirectUri );
511
- registeredRedirect .port (requestedRedirect .getPort ());
512
- if (registeredRedirect .build ().toString ().equals (requestedRedirect .toString ())) {
513
- return true ;
514
- }
515
- }
516
- return false ;
517
- }
518
-
519
- private static boolean isLoopbackAddress (String host ) {
520
- // IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
521
- if ("[0:0:0:0:0:0:0:1]" .equals (host ) || "[::1]" .equals (host )) {
522
- return true ;
523
- }
524
- // IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
525
- String [] ipv4Octets = host .split ("\\ ." );
526
- if (ipv4Octets .length != 4 ) {
527
- return false ;
528
- }
529
- try {
530
- int [] address = new int [ipv4Octets .length ];
531
- for (int i =0 ; i < ipv4Octets .length ; i ++) {
532
- address [i ] = Integer .parseInt (ipv4Octets [i ]);
533
- }
534
- return address [0 ] == 127 && address [1 ] >= 0 && address [1 ] <= 255 && address [2 ] >= 0 &&
535
- address [2 ] <= 255 && address [3 ] >= 1 && address [3 ] <= 255 ;
536
- } catch (NumberFormatException ex ) {
537
- return false ;
538
- }
539
- }
540
-
541
478
private static boolean isPrincipalAuthenticated (Authentication principal ) {
542
479
return principal != null &&
543
480
!AnonymousAuthenticationToken .class .isAssignableFrom (principal .getClass ()) &&
@@ -560,9 +497,16 @@ private static void throwError(String errorCode, String parameterName,
560
497
private static void throwError (String errorCode , String parameterName , String errorUri ,
561
498
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication ,
562
499
RegisteredClient registeredClient , OAuth2AuthorizationRequest authorizationRequest ) {
500
+ OAuth2Error error = new OAuth2Error (errorCode , "OAuth 2.0 Parameter: " + parameterName , errorUri );
501
+ throwError (error , parameterName , authorizationCodeRequestAuthentication , registeredClient , authorizationRequest );
502
+ }
503
+
504
+ private static void throwError (OAuth2Error error , String parameterName ,
505
+ OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication ,
506
+ RegisteredClient registeredClient , OAuth2AuthorizationRequest authorizationRequest ) {
563
507
564
508
boolean redirectOnError = true ;
565
- if (errorCode .equals (OAuth2ErrorCodes .INVALID_REQUEST ) &&
509
+ if (error . getErrorCode () .equals (OAuth2ErrorCodes .INVALID_REQUEST ) &&
566
510
(parameterName .equals (OAuth2ParameterNames .CLIENT_ID ) ||
567
511
parameterName .equals (OAuth2ParameterNames .REDIRECT_URI ) ||
568
512
parameterName .equals (OAuth2ParameterNames .STATE ))) {
@@ -587,7 +531,6 @@ private static void throwError(String errorCode, String parameterName, String er
587
531
authorizationCodeRequestAuthenticationResult .setAuthenticated (authorizationCodeRequestAuthentication .isAuthenticated ());
588
532
}
589
533
590
- OAuth2Error error = new OAuth2Error (errorCode , "OAuth 2.0 Parameter: " + parameterName , errorUri );
591
534
throw new OAuth2AuthorizationCodeRequestAuthenticationException (error , authorizationCodeRequestAuthenticationResult );
592
535
}
593
536
@@ -637,16 +580,95 @@ public void validate(OAuth2AuthenticationContext authenticationContext) {
637
580
authenticationContext .getAuthentication ();
638
581
RegisteredClient registeredClient = authenticationContext .get (RegisteredClient .class );
639
582
640
- if (StringUtils .hasText (authorizationCodeRequestAuthentication .getRedirectUri ())) {
641
- if (!isValidRedirectUri (authorizationCodeRequestAuthentication .getRedirectUri (), registeredClient )) {
583
+ String requestedRedirectUri = authorizationCodeRequestAuthentication .getRedirectUri ();
584
+
585
+ if (StringUtils .hasText (requestedRedirectUri )) {
586
+ // ***** redirect_uri is available in authorization request
587
+
588
+ UriComponents requestedRedirect = null ;
589
+ try {
590
+ requestedRedirect = UriComponentsBuilder .fromUriString (requestedRedirectUri ).build ();
591
+ } catch (Exception ex ) { }
592
+ if (requestedRedirect == null || requestedRedirect .getFragment () != null ) {
642
593
throwError (OAuth2ErrorCodes .INVALID_REQUEST , OAuth2ParameterNames .REDIRECT_URI ,
643
594
authorizationCodeRequestAuthentication , registeredClient );
644
595
}
645
- } else if (authorizationCodeRequestAuthentication .getScopes ().contains (OidcScopes .OPENID ) ||
646
- registeredClient .getRedirectUris ().size () != 1 ) {
647
- // redirect_uri is REQUIRED for OpenID Connect
648
- throwError (OAuth2ErrorCodes .INVALID_REQUEST , OAuth2ParameterNames .REDIRECT_URI ,
649
- authorizationCodeRequestAuthentication , registeredClient );
596
+
597
+ String requestedRedirectHost = requestedRedirect .getHost ();
598
+ if (requestedRedirectHost == null || requestedRedirectHost .equals ("localhost" )) {
599
+ // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1
600
+ // While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
601
+ // function similarly to loopback IP redirects described in Section 10.3.3,
602
+ // the use of "localhost" is NOT RECOMMENDED.
603
+ OAuth2Error error = new OAuth2Error (
604
+ OAuth2ErrorCodes .INVALID_REQUEST ,
605
+ "localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " +
606
+ "Use the IP literal (127.0.0.1) instead." ,
607
+ "https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1" );
608
+ throwError (error , OAuth2ParameterNames .REDIRECT_URI ,
609
+ authorizationCodeRequestAuthentication , registeredClient , null );
610
+ }
611
+
612
+ if (!isLoopbackAddress (requestedRedirectHost )) {
613
+ // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7
614
+ // When comparing client redirect URIs against pre-registered URIs,
615
+ // authorization servers MUST utilize exact string matching.
616
+ if (!registeredClient .getRedirectUris ().contains (requestedRedirectUri )) {
617
+ throwError (OAuth2ErrorCodes .INVALID_REQUEST , OAuth2ParameterNames .REDIRECT_URI ,
618
+ authorizationCodeRequestAuthentication , registeredClient );
619
+ }
620
+ } else {
621
+ // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-10.3.3
622
+ // The authorization server MUST allow any port to be specified at the
623
+ // time of the request for loopback IP redirect URIs, to accommodate
624
+ // clients that obtain an available ephemeral port from the operating
625
+ // system at the time of the request.
626
+ boolean validRedirectUri = false ;
627
+ for (String registeredRedirectUri : registeredClient .getRedirectUris ()) {
628
+ UriComponentsBuilder registeredRedirect = UriComponentsBuilder .fromUriString (registeredRedirectUri );
629
+ registeredRedirect .port (requestedRedirect .getPort ());
630
+ if (registeredRedirect .build ().toString ().equals (requestedRedirect .toString ())) {
631
+ validRedirectUri = true ;
632
+ break ;
633
+ }
634
+ }
635
+ if (!validRedirectUri ) {
636
+ throwError (OAuth2ErrorCodes .INVALID_REQUEST , OAuth2ParameterNames .REDIRECT_URI ,
637
+ authorizationCodeRequestAuthentication , registeredClient );
638
+ }
639
+ }
640
+
641
+ } else {
642
+ // ***** redirect_uri is NOT available in authorization request
643
+
644
+ if (authorizationCodeRequestAuthentication .getScopes ().contains (OidcScopes .OPENID ) ||
645
+ registeredClient .getRedirectUris ().size () != 1 ) {
646
+ // redirect_uri is REQUIRED for OpenID Connect
647
+ throwError (OAuth2ErrorCodes .INVALID_REQUEST , OAuth2ParameterNames .REDIRECT_URI ,
648
+ authorizationCodeRequestAuthentication , registeredClient );
649
+ }
650
+ }
651
+ }
652
+
653
+ private static boolean isLoopbackAddress (String host ) {
654
+ // IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
655
+ if ("[0:0:0:0:0:0:0:1]" .equals (host ) || "[::1]" .equals (host )) {
656
+ return true ;
657
+ }
658
+ // IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
659
+ String [] ipv4Octets = host .split ("\\ ." );
660
+ if (ipv4Octets .length != 4 ) {
661
+ return false ;
662
+ }
663
+ try {
664
+ int [] address = new int [ipv4Octets .length ];
665
+ for (int i =0 ; i < ipv4Octets .length ; i ++) {
666
+ address [i ] = Integer .parseInt (ipv4Octets [i ]);
667
+ }
668
+ return address [0 ] == 127 && address [1 ] >= 0 && address [1 ] <= 255 && address [2 ] >= 0 &&
669
+ address [2 ] <= 255 && address [3 ] >= 1 && address [3 ] <= 255 ;
670
+ } catch (NumberFormatException ex ) {
671
+ return false ;
650
672
}
651
673
}
652
674
0 commit comments