Skip to content

Commit 712bae9

Browse files
committed
Add ClientRegistration codeChallengeMethod to Enable PKCE
Closes gh-16382 Signed-off-by: DingHao <[email protected]>
1 parent 0e3cfd1 commit 712bae9

File tree

5 files changed

+71
-8
lines changed

5 files changed

+71
-8
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@ public ClientRegistration deserialize(JsonParser parser, DeserializationContext
7373
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
7474
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
7575
JsonNodeUtils.STRING_OBJECT_MAP, mapper))
76+
.codeChallengeMethod(JsonNodeUtils.findStringValue(clientRegistrationNode, "codeChallengeMethod"))
7677
.build();
7778
}
7879

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -71,6 +71,8 @@ public final class ClientRegistration implements Serializable {
7171

7272
private String clientName;
7373

74+
private String codeChallengeMethod;
75+
7476
private ClientRegistration() {
7577
}
7678

@@ -162,6 +164,14 @@ public String getClientName() {
162164
return this.clientName;
163165
}
164166

167+
/**
168+
* Returns the codeChallengeMethod of the client or registration.
169+
* @return the codeChallengeMethod
170+
*/
171+
public String getCodeChallengeMethod() {
172+
return this.codeChallengeMethod;
173+
}
174+
165175
@Override
166176
public String toString() {
167177
// @formatter:off
@@ -175,6 +185,7 @@ public String toString() {
175185
+ '\'' + ", scopes=" + this.scopes
176186
+ ", providerDetails=" + this.providerDetails
177187
+ ", clientName='" + this.clientName + '\''
188+
+ ", codeChallengeMethod='" + this.codeChallengeMethod + '\''
178189
+ '}';
179190
// @formatter:on
180191
}
@@ -367,6 +378,8 @@ public static final class Builder implements Serializable {
367378

368379
private String clientName;
369380

381+
private String codeChallengeMethod;
382+
370383
private Builder(String registrationId) {
371384
this.registrationId = registrationId;
372385
}
@@ -391,6 +404,7 @@ private Builder(ClientRegistration clientRegistration) {
391404
this.configurationMetadata = new HashMap<>(configurationMetadata);
392405
}
393406
this.clientName = clientRegistration.clientName;
407+
this.codeChallengeMethod = clientRegistration.codeChallengeMethod;
394408
}
395409

396410
/**
@@ -594,6 +608,16 @@ public Builder clientName(String clientName) {
594608
return this;
595609
}
596610

611+
/**
612+
* Sets the codeChallengeMethod of the client or registration.
613+
* @param codeChallengeMethod the codeChallengeMethod
614+
* @return the {@link Builder}
615+
*/
616+
public Builder codeChallengeMethod(String codeChallengeMethod) {
617+
this.codeChallengeMethod = codeChallengeMethod;
618+
return this;
619+
}
620+
597621
/**
598622
* Builds a new {@link ClientRegistration}.
599623
* @return a {@link ClientRegistration}
@@ -627,12 +651,13 @@ private ClientRegistration create() {
627651
clientRegistration.providerDetails = createProviderDetails(clientRegistration);
628652
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
629653
: this.registrationId;
654+
clientRegistration.codeChallengeMethod = this.codeChallengeMethod;
630655
return clientRegistration;
631656
}
632657

633658
private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) {
634659
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
635-
&& !StringUtils.hasText(this.clientSecret)) {
660+
&& (!StringUtils.hasText(this.clientSecret) || StringUtils.hasText(this.codeChallengeMethod))) {
636661
return ClientAuthenticationMethod.NONE;
637662
}
638663
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
3434
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
3535
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
3636
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
37+
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
3738
import org.springframework.security.oauth2.core.oidc.OidcScopes;
3839
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
3940
import org.springframework.security.web.util.UrlUtils;
@@ -185,6 +186,10 @@ private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientR
185186
}
186187
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
187188
DEFAULT_PKCE_APPLIER.accept(builder);
189+
if (StringUtils.hasText(clientRegistration.getCodeChallengeMethod())) {
190+
builder.additionalParameters((params) -> params.put(PkceParameterNames.CODE_CHALLENGE_METHOD,
191+
clientRegistration.getCodeChallengeMethod()));
192+
}
188193
}
189194
return builder;
190195
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ private static String asJson(ClientRegistration clientRegistration) {
276276
" " + configurationMetadata + "\n" +
277277
" }\n" +
278278
" },\n" +
279-
" \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" +
279+
" \"clientName\": \"" + clientRegistration.getClientName() + "\",\n" +
280+
" \"codeChallengeMethod\": " + ((clientRegistration.getCodeChallengeMethod() != null) ? "\"" + clientRegistration.getCodeChallengeMethod() + "\"" : null) + "\n" +
280281
"}";
281282
// @formatter:on
282283
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -56,6 +56,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
5656

5757
private ClientRegistration registration2;
5858

59+
private ClientRegistration pkceClientRegistration;
60+
5961
private ClientRegistration fineRedirectUriTemplateRegistration;
6062

6163
private ClientRegistration publicClientRegistration;
@@ -72,6 +74,9 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
7274
public void setUp() {
7375
this.registration1 = TestClientRegistrations.clientRegistration().build();
7476
this.registration2 = TestClientRegistrations.clientRegistration2().build();
77+
78+
this.pkceClientRegistration = pkceClientRegistration().build();
79+
7580
this.fineRedirectUriTemplateRegistration = fineRedirectUriTemplateClientRegistration().build();
7681
// @formatter:off
7782
this.publicClientRegistration = TestClientRegistrations.clientRegistration()
@@ -86,8 +91,8 @@ public void setUp() {
8691
.build();
8792
// @formatter:on
8893
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1,
89-
this.registration2, this.fineRedirectUriTemplateRegistration, this.publicClientRegistration,
90-
this.oidcRegistration);
94+
this.registration2, this.pkceClientRegistration, this.fineRedirectUriTemplateRegistration,
95+
this.publicClientRegistration, this.oidcRegistration);
9196
this.resolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository,
9297
this.authorizationRequestBaseUri);
9398
}
@@ -563,6 +568,32 @@ public void resolveWhenAuthorizationRequestCustomizerOverridesParameterThenQuery
563568
+ "nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" + "appid=client-id");
564569
}
565570

571+
@Test
572+
public void resolveWhenAuthorizationRequestProvideCodeChallengeMethod() {
573+
ClientRegistration clientRegistration = this.pkceClientRegistration;
574+
String requestUri = this.authorizationRequestBaseUri + "/" + clientRegistration.getRegistrationId();
575+
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
576+
request.setServletPath(requestUri);
577+
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
578+
assertThat(authorizationRequest.getAdditionalParameters().get(PkceParameterNames.CODE_CHALLENGE_METHOD))
579+
.isEqualTo(clientRegistration.getCodeChallengeMethod());
580+
}
581+
582+
private static ClientRegistration.Builder pkceClientRegistration() {
583+
return ClientRegistration.withRegistrationId("pkce")
584+
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
585+
.codeChallengeMethod("S256")
586+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
587+
.scope("read:user")
588+
.authorizationUri("https://example.com/login/oauth/authorize")
589+
.tokenUri("https://example.com/login/oauth/access_token")
590+
.userInfoUri("https://api.example.com/user")
591+
.userNameAttributeName("id")
592+
.clientName("Client Name")
593+
.clientId("client-id-3")
594+
.clientSecret("client-secret");
595+
}
596+
566597
private static ClientRegistration.Builder fineRedirectUriTemplateClientRegistration() {
567598
// @formatter:off
568599
return ClientRegistration.withRegistrationId("fine-redirect-uri-template-client-registration")

0 commit comments

Comments
 (0)