1818import java .security .Principal ;
1919import java .time .Instant ;
2020import java .time .temporal .ChronoUnit ;
21+ import java .util .Arrays ;
2122import java .util .Base64 ;
2223import java .util .Collections ;
2324import java .util .HashMap ;
2627import java .util .Set ;
2728import java .util .function .Function ;
2829import java .util .function .Supplier ;
29- import java .util .regex .Pattern ;
3030
3131import org .springframework .security .authentication .AnonymousAuthenticationToken ;
3232import org .springframework .security .authentication .AuthenticationProvider ;
3535import org .springframework .security .crypto .keygen .Base64StringKeyGenerator ;
3636import org .springframework .security .crypto .keygen .StringKeyGenerator ;
3737import org .springframework .security .oauth2 .core .AuthorizationGrantType ;
38+ import org .springframework .security .oauth2 .core .OAuth2AuthorizationCode ;
3839import org .springframework .security .oauth2 .core .OAuth2Error ;
3940import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
4041import org .springframework .security .oauth2 .core .OAuth2TokenType ;
4546import org .springframework .security .oauth2 .core .endpoint .PkceParameterNames ;
4647import org .springframework .security .oauth2 .core .oidc .OidcScopes ;
4748import org .springframework .security .oauth2 .server .authorization .OAuth2Authorization ;
48- import org .springframework .security .oauth2 .core .OAuth2AuthorizationCode ;
4949import org .springframework .security .oauth2 .server .authorization .OAuth2AuthorizationConsent ;
5050import org .springframework .security .oauth2 .server .authorization .OAuth2AuthorizationConsentService ;
5151import org .springframework .security .oauth2 .server .authorization .OAuth2AuthorizationService ;
7272public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implements AuthenticationProvider {
7373 private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType (OAuth2ParameterNames .STATE );
7474 private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1" ;
75- private static final Pattern LOOPBACK_ADDRESS_PATTERN =
76- Pattern .compile ("^127(?:\\ .[0-9]+){0,2}\\ .[0-9]+$|^\\ [(?:0*:)*?:?0*1]$" );
7775 private static final StringKeyGenerator DEFAULT_AUTHORIZATION_CODE_GENERATOR =
7876 new Base64StringKeyGenerator (Base64 .getUrlEncoder ().withoutPadding (), 96 );
7977 private static final StringKeyGenerator DEFAULT_STATE_GENERATOR =
@@ -417,7 +415,7 @@ private static boolean isValidRedirectUri(String requestedRedirectUri, Registere
417415 // redirects described in Section 10.3.3, the use of "localhost" is NOT RECOMMENDED.
418416 return false ;
419417 }
420- if (!LOOPBACK_ADDRESS_PATTERN . matcher (requestedRedirectHost ). matches ( )) {
418+ if (!isLoopbackAddress (requestedRedirectHost )) {
421419 // As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7
422420 // When comparing client redirect URIs against pre-registered URIs,
423421 // authorization servers MUST utilize exact string matching.
@@ -439,6 +437,25 @@ private static boolean isValidRedirectUri(String requestedRedirectUri, Registere
439437 return false ;
440438 }
441439
440+ private static boolean isLoopbackAddress (String host ) {
441+ // IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
442+ if ("[0:0:0:0:0:0:0:1]" .equals (host ) || "[::1]" .equals (host )) {
443+ return true ;
444+ }
445+ // IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
446+ String [] ipv4Octets = host .split ("\\ ." );
447+ if (ipv4Octets .length != 4 ) {
448+ return false ;
449+ }
450+ try {
451+ int [] address = Arrays .stream (ipv4Octets ).mapToInt (Integer ::parseInt ).toArray ();
452+ return address [0 ] == 127 && address [1 ] >= 0 && address [1 ] <= 255 && address [2 ] >= 0 &&
453+ address [2 ] <= 255 && address [3 ] >= 1 && address [3 ] <= 255 ;
454+ } catch (NumberFormatException ex ) {
455+ return false ;
456+ }
457+ }
458+
442459 private static boolean isPrincipalAuthenticated (Authentication principal ) {
443460 return principal != null &&
444461 !AnonymousAuthenticationToken .class .isAssignableFrom (principal .getClass ()) &&
0 commit comments