Spring boot/security encoding introspection credentials incorrectly
Describe the bug I am upgrading Spring boot from 3.3.5 to 3.5.8. This is incrementing Spring security from 6.3.4 to 6.5.7. The introspection credentials in the Basic Auth header are being encoded incorrectly after the upgrade.
application.yml
spring: security: oauth2: resourceserver: opaquetoken: introspection-uri: http://localhost:7171/introspect client-id: someClientId client-secret: h25spw7I_y0Kt=s5NPo
Spring boot 3.3.5 / Spring security 6.3.4 Encoded Authorization: Basic c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3Q9czVOUG8=
Spring boot 3.5.8 / Spring security 6.5.7 Encoded Authorization: Basic c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3QlM0RzNU5Qbw==
To Reproduce Link to project to reproduce. Run the application and invoke the example RestController. Change version of spring-boot-starter-parent from 3.3.5 to 3.5.8.
Expected behavior The encoded credentials should be encoded correctly as they were before, i.e. someClientId:h25spw7I_y0Kt=s5NPo should be encoded as: c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3Q9czVOUG8= and not: c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3QlM0RzNU5Qbw==
The encoding bug was introduced in: Spring boot - 3.5.0 Spring security - 6.5.0
Sample
https://github.com/ianHowlett1/spring-security-oauth-endcoding-bug
I think it's related to https://github.com/spring-projects/spring-security/releases/tag/6.5.0-M1
Encode clientId and clientSecret for OpaqueTokenIntrospector and ReactiveOpaqueTokenIntrospector
clientId and clientSecret will be encoded, and the result is c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3QlM0RzNU5Qbw==
Hi, @ianHowlett1. Thank you for the report and for your sample. Can you clarify how I should use your sample to verify the issue? Give that the change to SpringOpaqueTokenIntrospector is only additive, I'm curious to learn how the encoding changed between those two releases.
Hi @jzheaux, The encoding behaviour prior using spring boot 3.3.5:
Run the Application class in an IDE, I use Intellij. Invoke the api created on this path:
PUT http://localhost:8080/example. Then check the logs, the line in the logs to look at are:
2025-12-15T17:38:02.312Z DEBUG 33260 --- [spring-security-oauth-endcoding-bug] [nio-8080-exec-3] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@11a58ef68 pairs: {POST /introspect HTTP/1.1: null}{Accept: application/json}{Content-Type: application/x-www-form-urlencoded;charset=UTF-8}{Authorization: Basic c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3Q9czVOUG8=}{User-Agent: Java/17.0.12}{Host: localhost:7171}{Connection: keep-alive}{Content-Length: 94}
The client-id and client-secret in the application.yml are being encoded as:
c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3Q9czVOUG8=
To recreate issue:
In the pom.xml amend from:
<artifactId>spring-boot-starter-parent</artifactId>
<artifactId>spring-boot-starter-parent</artifactId>
Run the Application class again in the IDE. Invoke the api again on the same path
PUT http://localhost:8080/example. Then check the logs, the line in the logs to look at are:
025-12-15T17:49:06.846Z DEBUG 11380 --- [spring-security-oauth-endcoding-bug] [nio-8080-exec-3] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@18246df68 pairs: {POST /introspect HTTP/1.1: null}{Accept: application/json}{Content-Type: application/x-www-form-urlencoded}{Authorization: Basic c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3QlM0RzNU5Qbw==}{User-Agent: Java/17.0.12}{Host: localhost:7171}{Connection: keep-alive}{Content-Length: 94}
The client-id and client-secret are now being encoded as:
c29tZUNsaWVudElkOmgyNXNwdzdJX3kwS3QlM0RzNU5Qbw==
I think the issue is in the Builder in SpringOpaqueTokenIntrospector in spring-security-oauth2-resource-server-6.5.0.jar, these methods:
public Builder clientId(String clientId) {
Assert.notNull(clientId, "clientId cannot be null");
this.clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
return this;
}
public Builder clientSecret(String clientSecret) {
Assert.notNull(clientSecret, "clientSecret cannot be null");
this.clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
return this;
}
URL encoding the clientId and clientSecret is encoding the "=" in the clientSecret to "%3D". This could also include other characters.
The clientId and clientSecret are then used in the below method:
public SpringOpaqueTokenIntrospector build() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
return new SpringOpaqueTokenIntrospector(this.introspectionUri, restTemplate);
}
These credentials are then base64 encoded in the BasicAuthenticationInterceptor. As the "=" has been changed to "%3D", when my security server base64 decodes these, the clientSecret has been changed from:
h25spw7I_y0Kt=s5NPo
to
h25spw7I_y0Kt%3Ds5NPo
And rejects it.
Please let me know, if there is anything else I can help with.