Skip to content

Commit 9f52814

Browse files
Encode clientId and clientSecret
Closes spring-projectsgh-15988
1 parent 1782668 commit 9f52814

File tree

8 files changed

+120
-8
lines changed

8 files changed

+120
-8
lines changed

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.resource.introspection;
1818

1919
import java.net.URI;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
2022
import java.time.Instant;
2123
import java.util.ArrayList;
2224
import java.util.Collection;
@@ -82,8 +84,10 @@ public NimbusOpaqueTokenIntrospector(String introspectionUri, String clientId, S
8284
Assert.notNull(clientId, "clientId cannot be null");
8385
Assert.notNull(clientSecret, "clientSecret cannot be null");
8486
this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri));
87+
String encodeClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
88+
String encodeClientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
8589
RestTemplate restTemplate = new RestTemplate();
86-
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret));
90+
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(encodeClientId, encodeClientSecret));
8791
this.restOperations = restTemplate;
8892
}
8993

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.resource.introspection;
1818

1919
import java.net.URI;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
2022
import java.time.Instant;
2123
import java.util.ArrayList;
2224
import java.util.Collection;
@@ -79,7 +81,11 @@ public NimbusReactiveOpaqueTokenIntrospector(String introspectionUri, String cli
7981
Assert.hasText(clientId, "clientId cannot be empty");
8082
Assert.notNull(clientSecret, "clientSecret cannot be null");
8183
this.introspectionUri = URI.create(introspectionUri);
82-
this.webClient = WebClient.builder().defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret)).build();
84+
String encodeClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
85+
String encodeClientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
86+
this.webClient = WebClient.builder()
87+
.defaultHeaders((h) -> h.setBasicAuth(encodeClientId, encodeClientSecret))
88+
.build();
8389
}
8490

8591
/**

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.resource.introspection;
1818

1919
import java.net.URI;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
2022
import java.time.Instant;
2123
import java.util.ArrayList;
2224
import java.util.Arrays;
@@ -84,8 +86,10 @@ public SpringOpaqueTokenIntrospector(String introspectionUri, String clientId, S
8486
Assert.notNull(clientId, "clientId cannot be null");
8587
Assert.notNull(clientSecret, "clientSecret cannot be null");
8688
this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri));
89+
String encodeClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
90+
String encodeClientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
8791
RestTemplate restTemplate = new RestTemplate();
88-
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret));
92+
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(encodeClientId, encodeClientSecret));
8993
this.restOperations = restTemplate;
9094
}
9195

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.resource.introspection;
1818

1919
import java.net.URI;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
2022
import java.time.Instant;
2123
import java.util.ArrayList;
2224
import java.util.Arrays;
@@ -79,7 +81,11 @@ public SpringReactiveOpaqueTokenIntrospector(String introspectionUri, String cli
7981
Assert.hasText(clientId, "clientId cannot be empty");
8082
Assert.notNull(clientSecret, "clientSecret cannot be null");
8183
this.introspectionUri = URI.create(introspectionUri);
82-
this.webClient = WebClient.builder().defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret)).build();
84+
String encodeClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
85+
String encodeClientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
86+
this.webClient = WebClient.builder()
87+
.defaultHeaders((h) -> h.setBasicAuth(encodeClientId, encodeClientSecret))
88+
.build();
8389
}
8490

8591
/**

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -343,6 +343,29 @@ public void handleNonJsonContentType(String type) {
343343
.isThrownBy(() -> introspectionClient.introspect("sometokenhere"));
344344
}
345345

346+
@Test
347+
public void encodeUsernameAndPassword() throws Exception {
348+
try (MockWebServer server = new MockWebServer()) {
349+
String response = """
350+
{
351+
"active": true,
352+
"username": "client%&1"
353+
}
354+
""";
355+
server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response));
356+
String introspectUri = server.url("/introspect").toString();
357+
OpaqueTokenIntrospector introspectionClient = new NimbusOpaqueTokenIntrospector(introspectUri, "client%&1",
358+
"secret@$2");
359+
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token");
360+
// @formatter:off
361+
assertThat(authority.getAttributes())
362+
.isNotNull()
363+
.containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true)
364+
.containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client%&1");
365+
// @formatter:on
366+
}
367+
}
368+
346369
private static ResponseEntity<String> response(String content) {
347370
HttpHeaders headers = new HttpHeaders();
348371
headers.setContentType(MediaType.APPLICATION_JSON);

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -260,6 +260,29 @@ public void handleNonJsonContentType(String type) {
260260
.isThrownBy(() -> introspectionClient.introspect("sometokenhere").block());
261261
}
262262

263+
@Test
264+
public void encodeUsernameAndPassword() throws Exception {
265+
try (MockWebServer server = new MockWebServer()) {
266+
String response = """
267+
{
268+
"active": true,
269+
"username": "client%&1"
270+
}
271+
""";
272+
server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response));
273+
String introspectUri = server.url("/introspect").toString();
274+
ReactiveOpaqueTokenIntrospector introspectionClient = new NimbusReactiveOpaqueTokenIntrospector(
275+
introspectUri, "client%&1", "secret@$2");
276+
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block();
277+
// @formatter:off
278+
assertThat(authority.getAttributes())
279+
.isNotNull()
280+
.containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true)
281+
.containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client%&1");
282+
// @formatter:on
283+
}
284+
}
285+
263286
private WebClient mockResponse(String response) {
264287
return mockResponse(response, MediaType.APPLICATION_JSON_VALUE);
265288
}

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,29 @@ public void setAuthenticationConverterWhenNonNullConverterGivenThenConverterUsed
339339
verify(authenticationConverter).convert(any());
340340
}
341341

342+
@Test
343+
public void encodeUsernameAndPassword() throws Exception {
344+
try (MockWebServer server = new MockWebServer()) {
345+
String response = """
346+
{
347+
"active": true,
348+
"username": "client%&1"
349+
}
350+
""";
351+
server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response));
352+
String introspectUri = server.url("/introspect").toString();
353+
OpaqueTokenIntrospector introspectionClient = new SpringOpaqueTokenIntrospector(introspectUri, "client%&1",
354+
"secret@$2");
355+
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token");
356+
// @formatter:off
357+
assertThat(authority.getAttributes())
358+
.isNotNull()
359+
.containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true)
360+
.containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client%&1");
361+
// @formatter:on
362+
}
363+
}
364+
342365
private static ResponseEntity<Map<String, Object>> response(String content) {
343366
HttpHeaders headers = new HttpHeaders();
344367
headers.setContentType(MediaType.APPLICATION_JSON);

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,29 @@ public void constructorWhenRestOperationsIsNullThenIllegalArgumentException() {
261261
.isThrownBy(() -> new SpringReactiveOpaqueTokenIntrospector(INTROSPECTION_URL, null));
262262
}
263263

264+
@Test
265+
public void encodeUsernameAndPassword() throws Exception {
266+
try (MockWebServer server = new MockWebServer()) {
267+
String response = """
268+
{
269+
"active": true,
270+
"username": "client%&1"
271+
}
272+
""";
273+
server.setDispatcher(requiresAuth("client%25%261", "secret%40%242", response));
274+
String introspectUri = server.url("/introspect").toString();
275+
ReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector(
276+
introspectUri, "client%&1", "secret@$2");
277+
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block();
278+
// @formatter:off
279+
assertThat(authority.getAttributes())
280+
.isNotNull()
281+
.containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true)
282+
.containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "client%&1");
283+
// @formatter:on
284+
}
285+
}
286+
264287
private WebClient mockResponse(String response) {
265288
return mockResponse(toMap(response));
266289
}

0 commit comments

Comments
 (0)