mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 01:02:14 +00:00
Add ClientRegistration.clientSettings.requireProofKey to Enable PKCE
Closes gh-16382 Signed-off-by: DingHao <dh.hiekn@gmail.com>
This commit is contained in:
parent
8acd1d3f51
commit
8d3e0844c5
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -71,6 +71,8 @@ public final class ClientRegistration implements Serializable {
|
||||
|
||||
private String clientName;
|
||||
|
||||
private ClientSettings clientSettings;
|
||||
|
||||
private ClientRegistration() {
|
||||
}
|
||||
|
||||
@ -162,6 +164,14 @@ public final class ClientRegistration implements Serializable {
|
||||
return this.clientName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientSettings client configuration settings}.
|
||||
* @return the {@link ClientSettings}
|
||||
*/
|
||||
public ClientSettings getClientSettings() {
|
||||
return this.clientSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// @formatter:off
|
||||
@ -175,6 +185,7 @@ public final class ClientRegistration implements Serializable {
|
||||
+ '\'' + ", scopes=" + this.scopes
|
||||
+ ", providerDetails=" + this.providerDetails
|
||||
+ ", clientName='" + this.clientName + '\''
|
||||
+ ", clientSettings='" + this.clientSettings + '\''
|
||||
+ '}';
|
||||
// @formatter:on
|
||||
}
|
||||
@ -367,6 +378,8 @@ public final class ClientRegistration implements Serializable {
|
||||
|
||||
private String clientName;
|
||||
|
||||
private ClientSettings clientSettings;
|
||||
|
||||
private Builder(String registrationId) {
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
@ -391,6 +404,7 @@ public final class ClientRegistration implements Serializable {
|
||||
this.configurationMetadata = new HashMap<>(configurationMetadata);
|
||||
}
|
||||
this.clientName = clientRegistration.clientName;
|
||||
this.clientSettings = clientRegistration.clientSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -594,6 +608,16 @@ public final class ClientRegistration implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ClientSettings client configuration settings}.
|
||||
* @param clientSettings the client configuration settings
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder clientSettings(ClientSettings clientSettings) {
|
||||
this.clientSettings = clientSettings;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ClientRegistration}.
|
||||
* @return a {@link ClientRegistration}
|
||||
@ -627,12 +651,14 @@ public final class ClientRegistration implements Serializable {
|
||||
clientRegistration.providerDetails = createProviderDetails(clientRegistration);
|
||||
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
|
||||
: this.registrationId;
|
||||
clientRegistration.clientSettings = (this.clientSettings == null) ? ClientSettings.builder().build()
|
||||
: this.clientSettings;
|
||||
return clientRegistration;
|
||||
}
|
||||
|
||||
private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
|
||||
&& !StringUtils.hasText(this.clientSecret)) {
|
||||
&& (!StringUtils.hasText(this.clientSecret))) {
|
||||
return ClientAuthenticationMethod.NONE;
|
||||
}
|
||||
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.registration;
|
||||
|
||||
/**
|
||||
* A facility for client configuration settings.
|
||||
*
|
||||
* @author DingHao
|
||||
* @since 6.5
|
||||
*/
|
||||
public final class ClientSettings {
|
||||
|
||||
private boolean requireProofKey;
|
||||
|
||||
private ClientSettings() {
|
||||
|
||||
}
|
||||
|
||||
public boolean isRequireProofKey() {
|
||||
return this.requireProofKey;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private boolean requireProofKey;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if the client is required to provide a proof key challenge
|
||||
* and verifier when performing the Authorization Code Grant flow.
|
||||
* @param requireProofKey {@code true} if the client is required to provide a
|
||||
* proof key challenge and verifier, {@code false} otherwise
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder requireProofKey(boolean requireProofKey) {
|
||||
this.requireProofKey = requireProofKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientSettings build() {
|
||||
ClientSettings clientSettings = new ClientSettings();
|
||||
clientSettings.requireProofKey = this.requireProofKey;
|
||||
return clientSettings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -183,7 +183,8 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
|
||||
// value.
|
||||
applyNonce(builder);
|
||||
}
|
||||
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())
|
||||
|| clientRegistration.getClientSettings().isRequireProofKey()) {
|
||||
DEFAULT_PKCE_APPLIER.accept(builder);
|
||||
}
|
||||
return builder;
|
||||
|
@ -276,7 +276,10 @@ public class OAuth2AuthorizedClientMixinTests {
|
||||
" " + configurationMetadata + "\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" +
|
||||
" \"clientName\": \"" + clientRegistration.getClientName() + "\",\n" +
|
||||
" \"clientSettings\": {\n" +
|
||||
" \"requireProofKey\": " + clientRegistration.getClientSettings().isRequireProofKey() + "\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -28,6 +28,7 @@ import org.mockito.Mockito;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ClientSettings;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
@ -56,6 +57,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
||||
|
||||
private ClientRegistration registration2;
|
||||
|
||||
private ClientRegistration pkceClientRegistration;
|
||||
|
||||
private ClientRegistration fineRedirectUriTemplateRegistration;
|
||||
|
||||
private ClientRegistration publicClientRegistration;
|
||||
@ -72,6 +75,9 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
||||
public void setUp() {
|
||||
this.registration1 = TestClientRegistrations.clientRegistration().build();
|
||||
this.registration2 = TestClientRegistrations.clientRegistration2().build();
|
||||
|
||||
this.pkceClientRegistration = pkceClientRegistration().build();
|
||||
|
||||
this.fineRedirectUriTemplateRegistration = fineRedirectUriTemplateClientRegistration().build();
|
||||
// @formatter:off
|
||||
this.publicClientRegistration = TestClientRegistrations.clientRegistration()
|
||||
@ -86,8 +92,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1,
|
||||
this.registration2, this.fineRedirectUriTemplateRegistration, this.publicClientRegistration,
|
||||
this.oidcRegistration);
|
||||
this.registration2, this.pkceClientRegistration, this.fineRedirectUriTemplateRegistration,
|
||||
this.publicClientRegistration, this.oidcRegistration);
|
||||
this.resolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository,
|
||||
this.authorizationRequestBaseUri);
|
||||
}
|
||||
@ -563,6 +569,32 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
||||
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" + "appid=client-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenAuthorizationRequestProvideCodeChallengeMethod() {
|
||||
ClientRegistration clientRegistration = this.pkceClientRegistration;
|
||||
String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
|
||||
assertThat(authorizationRequest.getAdditionalParameters().containsKey(PkceParameterNames.CODE_CHALLENGE_METHOD))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
private static ClientRegistration.Builder pkceClientRegistration() {
|
||||
return ClientRegistration.withRegistrationId("pkce")
|
||||
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
|
||||
.clientSettings(ClientSettings.builder().requireProofKey(true).build())
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.scope("read:user")
|
||||
.authorizationUri("https://example.com/login/oauth/authorize")
|
||||
.tokenUri("https://example.com/login/oauth/access_token")
|
||||
.userInfoUri("https://api.example.com/user")
|
||||
.userNameAttributeName("id")
|
||||
.clientName("Client Name")
|
||||
.clientId("client-id-3")
|
||||
.clientSecret("client-secret");
|
||||
}
|
||||
|
||||
private static ClientRegistration.Builder fineRedirectUriTemplateClientRegistration() {
|
||||
// @formatter:off
|
||||
return ClientRegistration.withRegistrationId("fine-redirect-uri-template-client-registration")
|
||||
|
Loading…
x
Reference in New Issue
Block a user