diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java index 742325b3..2c8dc2ad 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,23 +100,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator impleme authorizationCodeRequestAuthentication, registeredClient); } - String requestedRedirectHost = requestedRedirect.getHost(); - if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) { - // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7.1 - // While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}") - // function similarly to loopback IP redirects described in Section 10.3.3, - // the use of "localhost" is NOT RECOMMENDED. - OAuth2Error error = new OAuth2Error( - OAuth2ErrorCodes.INVALID_REQUEST, - "localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " + - "Use the IP literal (127.0.0.1) instead.", - "https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7.1"); - throwError(error, OAuth2ParameterNames.REDIRECT_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - - if (!isLoopbackAddress(requestedRedirectHost)) { - // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7 + if (!isLoopbackAddress(requestedRedirect.getHost())) { + // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-22#section-4.1.3 // When comparing client redirect URIs against pre-registered URIs, // authorization servers MUST utilize exact string matching. if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { @@ -124,7 +109,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator impleme authorizationCodeRequestAuthentication, registeredClient); } } else { - // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-10.3.3 + // As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-08#section-8.4.2 // The authorization server MUST allow any port to be specified at the // time of the request for loopback IP redirect URIs, to accommodate // clients that obtain an available ephemeral port from the operating @@ -157,6 +142,9 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator impleme } private static boolean isLoopbackAddress(String host) { + if (!StringUtils.hasText(host)) { + return false; + } // IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1" if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) { return true; diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java index f2e9eb7e..b9e96da4 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java @@ -181,27 +181,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { ); } - // gh-243 - @Test - public void authenticateWhenRedirectUriLocalhostThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .thenReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = - new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), principal, - "https://localhost:5000", STATE, registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies(ex -> - assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null) - ) - .extracting(ex -> ((OAuth2AuthorizationCodeRequestAuthenticationException) ex).getError()) - .satisfies(error -> - assertThat(error.getDescription()).isEqualTo("localhost is not allowed for the redirect_uri (https://localhost:5000). Use the IP literal (127.0.0.1) instead.")); - } - @Test public void authenticateWhenUnregisteredRedirectUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();