|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * Copyright 2002-2020 the original author or authors.
|
|
|
|
|
|
+ * Copyright 2002-2022 the original author or authors.
|
|
*
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* you may not use this file except in compliance with the License.
|
|
@@ -32,11 +32,11 @@ import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
|
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
|
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
|
|
|
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers;
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
-import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
|
|
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
|
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
|
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
|
@@ -59,6 +59,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|
*
|
|
*
|
|
* @author Rob Winch
|
|
* @author Rob Winch
|
|
* @author Mark Heckler
|
|
* @author Mark Heckler
|
|
|
|
+ * @author Joe Grandja
|
|
* @since 5.1
|
|
* @since 5.1
|
|
*/
|
|
*/
|
|
public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOAuth2AuthorizationRequestResolver {
|
|
public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOAuth2AuthorizationRequestResolver {
|
|
@@ -78,14 +79,18 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
|
|
|
|
private static final char PATH_DELIMITER = '/';
|
|
private static final char PATH_DELIMITER = '/';
|
|
|
|
|
|
- private final ServerWebExchangeMatcher authorizationRequestMatcher;
|
|
|
|
|
|
+ private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator(
|
|
|
|
+ Base64.getUrlEncoder());
|
|
|
|
|
|
- private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
|
|
|
|
|
+ private static final StringKeyGenerator DEFAULT_SECURE_KEY_GENERATOR = new Base64StringKeyGenerator(
|
|
|
|
+ Base64.getUrlEncoder().withoutPadding(), 96);
|
|
|
|
|
|
- private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
|
|
|
|
|
+ private static final Consumer<OAuth2AuthorizationRequest.Builder> DEFAULT_PKCE_APPLIER = OAuth2AuthorizationRequestCustomizers
|
|
|
|
+ .withPkce();
|
|
|
|
|
|
- private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(
|
|
|
|
- Base64.getUrlEncoder().withoutPadding(), 96);
|
|
|
|
|
|
+ private final ServerWebExchangeMatcher authorizationRequestMatcher;
|
|
|
|
+
|
|
|
|
+ private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
|
|
|
|
|
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer = (customizer) -> {
|
|
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer = (customizer) -> {
|
|
};
|
|
};
|
|
@@ -133,7 +138,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange, String clientRegistrationId) {
|
|
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange, String clientRegistrationId) {
|
|
- return this.findByRegistrationId(exchange, clientRegistrationId)
|
|
|
|
|
|
+ return findByRegistrationId(exchange, clientRegistrationId)
|
|
.map((clientRegistration) -> authorizationRequest(exchange, clientRegistration));
|
|
.map((clientRegistration) -> authorizationRequest(exchange, clientRegistration));
|
|
}
|
|
}
|
|
|
|
|
|
@@ -143,6 +148,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
* @param authorizationRequestCustomizer the {@code Consumer} to be provided the
|
|
* @param authorizationRequestCustomizer the {@code Consumer} to be provided the
|
|
* {@link OAuth2AuthorizationRequest.Builder}
|
|
* {@link OAuth2AuthorizationRequest.Builder}
|
|
* @since 5.3
|
|
* @since 5.3
|
|
|
|
+ * @see OAuth2AuthorizationRequestCustomizers
|
|
*/
|
|
*/
|
|
public final void setAuthorizationRequestCustomizer(
|
|
public final void setAuthorizationRequestCustomizer(
|
|
Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer) {
|
|
Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer) {
|
|
@@ -159,17 +165,14 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
|
|
|
|
private OAuth2AuthorizationRequest authorizationRequest(ServerWebExchange exchange,
|
|
private OAuth2AuthorizationRequest authorizationRequest(ServerWebExchange exchange,
|
|
ClientRegistration clientRegistration) {
|
|
ClientRegistration clientRegistration) {
|
|
|
|
+ OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration);
|
|
String redirectUriStr = expandRedirectUri(exchange.getRequest(), clientRegistration);
|
|
String redirectUriStr = expandRedirectUri(exchange.getRequest(), clientRegistration);
|
|
- Map<String, Object> attributes = new HashMap<>();
|
|
|
|
- attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
|
|
|
|
- OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration, attributes);
|
|
|
|
// @formatter:off
|
|
// @formatter:off
|
|
builder.clientId(clientRegistration.getClientId())
|
|
builder.clientId(clientRegistration.getClientId())
|
|
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
|
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
|
.redirectUri(redirectUriStr)
|
|
.redirectUri(redirectUriStr)
|
|
.scopes(clientRegistration.getScopes())
|
|
.scopes(clientRegistration.getScopes())
|
|
- .state(this.stateGenerator.generateKey())
|
|
|
|
- .attributes(attributes);
|
|
|
|
|
|
+ .state(DEFAULT_STATE_GENERATOR.generateKey());
|
|
// @formatter:on
|
|
// @formatter:on
|
|
|
|
|
|
this.authorizationRequestCustomizer.accept(builder);
|
|
this.authorizationRequestCustomizer.accept(builder);
|
|
@@ -177,11 +180,13 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
return builder.build();
|
|
return builder.build();
|
|
}
|
|
}
|
|
|
|
|
|
- private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientRegistration,
|
|
|
|
- Map<String, Object> attributes) {
|
|
|
|
|
|
+ private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientRegistration) {
|
|
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
|
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
|
- OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode();
|
|
|
|
- Map<String, Object> additionalParameters = new HashMap<>();
|
|
|
|
|
|
+ // @formatter:off
|
|
|
|
+ OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode()
|
|
|
|
+ .attributes((attrs) ->
|
|
|
|
+ attrs.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
|
|
|
|
+ // @formatter:on
|
|
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())
|
|
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())
|
|
&& clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
|
|
&& clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
|
|
// Section 3.1.2.1 Authentication Request -
|
|
// Section 3.1.2.1 Authentication Request -
|
|
@@ -189,12 +194,11 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
// scope
|
|
// scope
|
|
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope
|
|
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope
|
|
// value.
|
|
// value.
|
|
- addNonceParameters(attributes, additionalParameters);
|
|
|
|
|
|
+ applyNonce(builder);
|
|
}
|
|
}
|
|
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
|
|
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
|
|
- addPkceParameters(attributes, additionalParameters);
|
|
|
|
|
|
+ DEFAULT_PKCE_APPLIER.accept(builder);
|
|
}
|
|
}
|
|
- builder.additionalParameters(additionalParameters);
|
|
|
|
return builder;
|
|
return builder;
|
|
}
|
|
}
|
|
if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
|
|
if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
|
|
@@ -261,54 +265,22 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
|
|
|
|
|
|
/**
|
|
/**
|
|
* Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests.
|
|
* Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests.
|
|
- * @param attributes where the {@link OidcParameterNames#NONCE} is stored for the
|
|
|
|
- * authentication request
|
|
|
|
- * @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is
|
|
|
|
- * added for the authentication request
|
|
|
|
|
|
+ * @param builder where the {@link OidcParameterNames#NONCE} and hash is stored for
|
|
|
|
+ * the authentication request
|
|
*
|
|
*
|
|
* @since 5.2
|
|
* @since 5.2
|
|
* @see <a target="_blank" href=
|
|
* @see <a target="_blank" href=
|
|
* "https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1.
|
|
* "https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1.
|
|
* Authentication Request</a>
|
|
* Authentication Request</a>
|
|
*/
|
|
*/
|
|
- private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
|
|
|
|
|
|
+ private static void applyNonce(OAuth2AuthorizationRequest.Builder builder) {
|
|
try {
|
|
try {
|
|
- String nonce = this.secureKeyGenerator.generateKey();
|
|
|
|
|
|
+ String nonce = DEFAULT_SECURE_KEY_GENERATOR.generateKey();
|
|
String nonceHash = createHash(nonce);
|
|
String nonceHash = createHash(nonce);
|
|
- attributes.put(OidcParameterNames.NONCE, nonce);
|
|
|
|
- additionalParameters.put(OidcParameterNames.NONCE, nonceHash);
|
|
|
|
- }
|
|
|
|
- catch (NoSuchAlgorithmException ex) {
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization
|
|
|
|
- * and Access Token Requests
|
|
|
|
- * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the
|
|
|
|
- * token request
|
|
|
|
- * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and,
|
|
|
|
- * usually, {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in
|
|
|
|
- * the authorization request.
|
|
|
|
- *
|
|
|
|
- * @since 5.2
|
|
|
|
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1.
|
|
|
|
- * Protocol Flow</a>
|
|
|
|
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1.
|
|
|
|
- * Client Creates a Code Verifier</a>
|
|
|
|
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2.
|
|
|
|
- * Client Creates the Code Challenge</a>
|
|
|
|
- */
|
|
|
|
- private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
|
|
|
|
- String codeVerifier = this.secureKeyGenerator.generateKey();
|
|
|
|
- attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
|
|
|
|
- try {
|
|
|
|
- String codeChallenge = createHash(codeVerifier);
|
|
|
|
- additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
|
|
|
|
- additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
|
|
|
|
|
+ builder.attributes((attrs) -> attrs.put(OidcParameterNames.NONCE, nonce));
|
|
|
|
+ builder.additionalParameters((params) -> params.put(OidcParameterNames.NONCE, nonceHash));
|
|
}
|
|
}
|
|
catch (NoSuchAlgorithmException ex) {
|
|
catch (NoSuchAlgorithmException ex) {
|
|
- additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|