Skip to content

Commit 333837d

Browse files
committed
Remove OAuth2AuthenticationValidator
Closes spring-projectsgh-891
1 parent f545816 commit 333837d

File tree

5 files changed

+253
-218
lines changed

5 files changed

+253
-218
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationValidator.java

-40
This file was deleted.

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationContext.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Collections;
1919
import java.util.HashMap;
2020
import java.util.Map;
21+
import java.util.function.Consumer;
2122

2223
import org.springframework.lang.Nullable;
2324
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -30,7 +31,8 @@
3031
* @author Joe Grandja
3132
* @since 0.4.0
3233
* @see OAuth2AuthenticationContext
33-
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
34+
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
35+
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer)
3436
*/
3537
public final class OAuth2AuthorizationCodeRequestAuthenticationContext implements OAuth2AuthenticationContext {
3638
private final Map<Object, Object> context;

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java

+17-165
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@
1919
import java.time.Instant;
2020
import java.util.Base64;
2121
import java.util.Collections;
22-
import java.util.HashMap;
2322
import java.util.HashSet;
24-
import java.util.Map;
2523
import java.util.Set;
2624
import java.util.function.Consumer;
27-
import java.util.function.Function;
2825

2926
import org.springframework.lang.Nullable;
3027
import org.springframework.security.authentication.AnonymousAuthenticationToken;
@@ -55,8 +52,6 @@
5552
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
5653
import org.springframework.util.Assert;
5754
import org.springframework.util.StringUtils;
58-
import org.springframework.web.util.UriComponents;
59-
import org.springframework.web.util.UriComponentsBuilder;
6055

6156
/**
6257
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request (and Consent)
@@ -66,6 +61,7 @@
6661
* @author Steve Riesenberg
6762
* @since 0.1.2
6863
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
64+
* @see OAuth2AuthorizationCodeRequestAuthenticationValidator
6965
* @see OAuth2AuthorizationCodeAuthenticationProvider
7066
* @see RegisteredClientRepository
7167
* @see OAuth2AuthorizationService
@@ -78,13 +74,12 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
7874
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
7975
private static final StringKeyGenerator DEFAULT_STATE_GENERATOR =
8076
new Base64StringKeyGenerator(Base64.getUrlEncoder());
81-
private static final Function<String, OAuth2AuthenticationValidator> DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER =
82-
createDefaultAuthenticationValidatorResolver();
8377
private final RegisteredClientRepository registeredClientRepository;
8478
private final OAuth2AuthorizationService authorizationService;
8579
private final OAuth2AuthorizationConsentService authorizationConsentService;
8680
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
87-
private Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver = DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER;
81+
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
82+
new OAuth2AuthorizationCodeRequestAuthenticationValidator();
8883
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
8984

9085
/**
@@ -131,23 +126,20 @@ public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2Authorizati
131126
}
132127

133128
/**
134-
* Sets the resolver that resolves an {@link OAuth2AuthenticationValidator} from the provided OAuth 2.0 Authorization Request parameter.
129+
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
130+
* and is responsible for validating specific OAuth 2.0 Authorization Request parameters
131+
* associated in the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
132+
* The default authentication validator is {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}.
135133
*
136134
* <p>
137-
* The following OAuth 2.0 Authorization Request parameters are supported:
138-
* <ol>
139-
* <li>{@link OAuth2ParameterNames#REDIRECT_URI}</li>
140-
* <li>{@link OAuth2ParameterNames#SCOPE}</li>
141-
* </ol>
135+
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
142136
*
143-
* <p>
144-
* <b>NOTE:</b> The resolved {@link OAuth2AuthenticationValidator} MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
145-
*
146-
* @param authenticationValidatorResolver the resolver that resolves an {@link OAuth2AuthenticationValidator} from the provided OAuth 2.0 Authorization Request parameter
137+
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for validating specific OAuth 2.0 Authorization Request parameters
138+
* @since 0.4.0
147139
*/
148-
public void setAuthenticationValidatorResolver(Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver) {
149-
Assert.notNull(authenticationValidatorResolver, "authenticationValidatorResolver cannot be null");
150-
this.authenticationValidatorResolver = authenticationValidatorResolver;
140+
public void setAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
141+
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
142+
this.authenticationValidator = authenticationValidator;
151143
}
152144

153145
/**
@@ -186,22 +178,17 @@ private Authentication authenticateAuthorizationRequest(Authentication authentic
186178
authorizationCodeRequestAuthentication, null);
187179
}
188180

189-
OAuth2AuthenticationContext authenticationContext =
181+
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext =
190182
OAuth2AuthorizationCodeRequestAuthenticationContext.with(authorizationCodeRequestAuthentication)
191183
.registeredClient(registeredClient)
192184
.build();
193-
194-
OAuth2AuthenticationValidator redirectUriValidator = resolveAuthenticationValidator(OAuth2ParameterNames.REDIRECT_URI);
195-
redirectUriValidator.validate(authenticationContext);
185+
this.authenticationValidator.accept(authenticationContext);
196186

197187
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
198188
throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID,
199189
authorizationCodeRequestAuthentication, registeredClient);
200190
}
201191

202-
OAuth2AuthenticationValidator scopeValidator = resolveAuthenticationValidator(OAuth2ParameterNames.SCOPE);
203-
scopeValidator.validate(authenticationContext);
204-
205192
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
206193
String codeChallenge = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get(PkceParameterNames.CODE_CHALLENGE);
207194
if (StringUtils.hasText(codeChallenge)) {
@@ -284,13 +271,6 @@ private Authentication authenticateAuthorizationRequest(Authentication authentic
284271
.build();
285272
}
286273

287-
private OAuth2AuthenticationValidator resolveAuthenticationValidator(String parameterName) {
288-
OAuth2AuthenticationValidator authenticationValidator = this.authenticationValidatorResolver.apply(parameterName);
289-
return authenticationValidator != null ?
290-
authenticationValidator :
291-
DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER.apply(parameterName);
292-
}
293-
294274
private Authentication authenticateAuthorizationConsent(Authentication authentication) throws AuthenticationException {
295275
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
296276
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
@@ -414,13 +394,6 @@ private Authentication authenticateAuthorizationConsent(Authentication authentic
414394
.build();
415395
}
416396

417-
private static Function<String, OAuth2AuthenticationValidator> createDefaultAuthenticationValidatorResolver() {
418-
Map<String, OAuth2AuthenticationValidator> authenticationValidators = new HashMap<>();
419-
authenticationValidators.put(OAuth2ParameterNames.REDIRECT_URI, new DefaultRedirectUriOAuth2AuthenticationValidator());
420-
authenticationValidators.put(OAuth2ParameterNames.SCOPE, new DefaultScopeOAuth2AuthenticationValidator());
421-
return authenticationValidators::get;
422-
}
423-
424397
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal,
425398
OAuth2AuthorizationRequest authorizationRequest) {
426399
return OAuth2Authorization.withRegisteredClient(registeredClient)
@@ -505,7 +478,6 @@ private static void throwError(OAuth2Error error, String parameterName,
505478
boolean redirectOnError = true;
506479
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
507480
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
508-
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI) ||
509481
parameterName.equals(OAuth2ParameterNames.STATE))) {
510482
redirectOnError = false;
511483
}
@@ -520,14 +492,14 @@ private static void throwError(OAuth2Error error, String parameterName,
520492
.redirectUri(redirectUri)
521493
.state(state)
522494
.build();
523-
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
524495
} else if (!redirectOnError && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
525496
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
526497
.redirectUri(null) // Prevent redirects
527498
.build();
528-
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
529499
}
530500

501+
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
502+
531503
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
532504
}
533505

@@ -569,124 +541,4 @@ public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
569541

570542
}
571543

572-
private static class DefaultRedirectUriOAuth2AuthenticationValidator implements OAuth2AuthenticationValidator {
573-
574-
@Override
575-
public void validate(OAuth2AuthenticationContext authenticationContext) {
576-
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
577-
authenticationContext.getAuthentication();
578-
RegisteredClient registeredClient = authenticationContext.get(RegisteredClient.class);
579-
580-
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
581-
582-
if (StringUtils.hasText(requestedRedirectUri)) {
583-
// ***** redirect_uri is available in authorization request
584-
585-
UriComponents requestedRedirect = null;
586-
try {
587-
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
588-
} catch (Exception ex) { }
589-
if (requestedRedirect == null || requestedRedirect.getFragment() != null) {
590-
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
591-
authorizationCodeRequestAuthentication, registeredClient);
592-
}
593-
594-
String requestedRedirectHost = requestedRedirect.getHost();
595-
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) {
596-
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1
597-
// While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
598-
// function similarly to loopback IP redirects described in Section 10.3.3,
599-
// the use of "localhost" is NOT RECOMMENDED.
600-
OAuth2Error error = new OAuth2Error(
601-
OAuth2ErrorCodes.INVALID_REQUEST,
602-
"localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " +
603-
"Use the IP literal (127.0.0.1) instead.",
604-
"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1");
605-
throwError(error, OAuth2ParameterNames.REDIRECT_URI,
606-
authorizationCodeRequestAuthentication, registeredClient, null);
607-
}
608-
609-
if (!isLoopbackAddress(requestedRedirectHost)) {
610-
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7
611-
// When comparing client redirect URIs against pre-registered URIs,
612-
// authorization servers MUST utilize exact string matching.
613-
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
614-
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
615-
authorizationCodeRequestAuthentication, registeredClient);
616-
}
617-
} else {
618-
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-10.3.3
619-
// The authorization server MUST allow any port to be specified at the
620-
// time of the request for loopback IP redirect URIs, to accommodate
621-
// clients that obtain an available ephemeral port from the operating
622-
// system at the time of the request.
623-
boolean validRedirectUri = false;
624-
for (String registeredRedirectUri : registeredClient.getRedirectUris()) {
625-
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri);
626-
registeredRedirect.port(requestedRedirect.getPort());
627-
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) {
628-
validRedirectUri = true;
629-
break;
630-
}
631-
}
632-
if (!validRedirectUri) {
633-
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
634-
authorizationCodeRequestAuthentication, registeredClient);
635-
}
636-
}
637-
638-
} else {
639-
// ***** redirect_uri is NOT available in authorization request
640-
641-
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) ||
642-
registeredClient.getRedirectUris().size() != 1) {
643-
// redirect_uri is REQUIRED for OpenID Connect
644-
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
645-
authorizationCodeRequestAuthentication, registeredClient);
646-
}
647-
}
648-
}
649-
650-
private static boolean isLoopbackAddress(String host) {
651-
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
652-
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) {
653-
return true;
654-
}
655-
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
656-
String[] ipv4Octets = host.split("\\.");
657-
if (ipv4Octets.length != 4) {
658-
return false;
659-
}
660-
try {
661-
int[] address = new int[ipv4Octets.length];
662-
for (int i=0; i < ipv4Octets.length; i++) {
663-
address[i] = Integer.parseInt(ipv4Octets[i]);
664-
}
665-
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 &&
666-
address[2] <= 255 && address[3] >= 1 && address[3] <= 255;
667-
} catch (NumberFormatException ex) {
668-
return false;
669-
}
670-
}
671-
672-
}
673-
674-
private static class DefaultScopeOAuth2AuthenticationValidator implements OAuth2AuthenticationValidator {
675-
676-
@Override
677-
public void validate(OAuth2AuthenticationContext authenticationContext) {
678-
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
679-
authenticationContext.getAuthentication();
680-
RegisteredClient registeredClient = authenticationContext.get(RegisteredClient.class);
681-
682-
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
683-
Set<String> allowedScopes = registeredClient.getScopes();
684-
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
685-
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
686-
authorizationCodeRequestAuthentication, registeredClient);
687-
}
688-
}
689-
690-
}
691-
692544
}

0 commit comments

Comments
 (0)