This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 950c270eb25 keep mtls certificate binding on refreshed access tokens
(#3652)
950c270eb25 is described below
commit 950c270eb25e9373b7dde7211d608dd20f043dce
Author: Naveed Khan <[email protected]>
AuthorDate: Sun Nov 1 21:16:23 2026 +0530
keep mtls certificate binding on refreshed access tokens (#3652)
* keep mtls certificate binding on refreshed access tokens
* test cnf claim on refreshed jwt and refresh without a certificate
---
.../oauth2/provider/AbstractOAuthDataProvider.java | 18 +++++---
.../provider/AbstractOAuthDataProviderTest.java | 54 ++++++++++++++++++++++
.../provider/JCacheJWTOAuthDataProviderTest.java | 38 +++++++++++++++
3 files changed, 104 insertions(+), 6 deletions(-)
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
index 32e61e174c3..130273f1343 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
@@ -85,6 +85,16 @@ public abstract class AbstractOAuthDataProvider implements
OAuthDataProvider, Cl
atReg.getClient(), atReg.getApprovedScope()),
atReg.getSubject());
+ setCertificateThumbprintConfirmation(at);
+
+ if (isUseJwtFormatForAccessTokens()) {
+ convertToJWTAccessToken(at);
+ }
+
+ return at;
+ }
+
+ private void setCertificateThumbprintConfirmation(ServerAccessToken at) {
if (messageContext != null) {
String certCnf =
(String)messageContext.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256);
if (certCnf != null) {
@@ -92,12 +102,6 @@ public abstract class AbstractOAuthDataProvider implements
OAuthDataProvider, Cl
at.getExtraProperties().put(JoseConstants.HEADER_X509_THUMBPRINT_SHA256,
certCnf);
}
}
-
- if (isUseJwtFormatForAccessTokens()) {
- convertToJWTAccessToken(at);
- }
-
- return at;
}
//CHECKSTYLE:OFF
@@ -475,6 +479,8 @@ public abstract class AbstractOAuthDataProvider implements
OAuthDataProvider, Cl
oldRefreshToken.getGrantType(), oldRefreshToken.getNonce(),
null, theNewScopes, oldRefreshToken.getSubject());
+ setCertificateThumbprintConfirmation(at);
+
if (isUseJwtFormatForAccessTokens()) {
convertToJWTAccessToken(at);
}
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
index 732fb18e58c..4406ba36958 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.rs.security.jose.common.JoseConstants;
import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
@@ -48,6 +50,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
abstract class AbstractOAuthDataProviderTest {
private static KeyPair keyPair;
@@ -403,6 +407,56 @@ abstract class AbstractOAuthDataProviderTest {
assertNotNull(getProvider().getAccessToken(atA.getTokenKey()));
}
+ @Test
+ public void testRefreshedAccessTokenKeepsCertificateBinding() {
+ MessageContext mc = mock(MessageContext.class);
+
when(mc.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)).thenReturn("thumbprint");
+ getProvider().setMessageContext(mc);
+
+ Client c = addClient("101", "bob");
+
+ AccessTokenRegistration atr = new AccessTokenRegistration();
+ atr.setClient(c);
+ atr.setApprovedScope(Arrays.asList("a", "refreshToken"));
+ atr.setSubject(c.getResourceOwnerSubject());
+
+ ServerAccessToken at = getProvider().createAccessToken(atr);
+ assertEquals("thumbprint",
at.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256));
+
+ ServerAccessToken at2 = getProvider().refreshAccessToken(c,
at.getRefreshToken(), Collections.emptyList());
+ assertEquals("thumbprint",
at2.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256));
+ }
+
+ /**
+ * The binding follows the certificate on the current request, as it does
for the initial grant,
+ * so a refresh without a certificate issues an unbound token.
+ */
+ @Test
+ public void testRefreshWithoutCertificateIssuesUnboundAccessToken() {
+ MessageContext mc = mock(MessageContext.class);
+
when(mc.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)).thenReturn("thumbprint");
+ getProvider().setMessageContext(mc);
+
+ Client c = addClient("101", "bob");
+
+ AccessTokenRegistration atr = new AccessTokenRegistration();
+ atr.setClient(c);
+ atr.setApprovedScope(Arrays.asList("a", "refreshToken"));
+ atr.setSubject(c.getResourceOwnerSubject());
+
+ ServerAccessToken at = getProvider().createAccessToken(atr);
+ assertEquals("thumbprint",
at.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256));
+
+
when(mc.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)).thenReturn(null);
+
+ ServerAccessToken at2 = getProvider().refreshAccessToken(c,
at.getRefreshToken(), Collections.emptyList());
+
assertNull(at2.getExtraProperties().get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256));
+ if (getProvider().isUseJwtFormatForAccessTokens()) {
+ JwtToken jwt = new
JwsJwtCompactConsumer(at2.getTokenKey()).getJwtToken();
+ assertNull(jwt.getClaim(JwtConstants.CLAIM_CONFIRMATION));
+ }
+ }
+
protected Client addClient(String clientId, String userLogin) {
Client c = new Client();
c.setRedirectUris(Collections.singletonList("http://client/redirect"));
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/JCacheJWTOAuthDataProviderTest.java
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/JCacheJWTOAuthDataProviderTest.java
index 53b7741a9d0..07f12b98d8b 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/JCacheJWTOAuthDataProviderTest.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/JCacheJWTOAuthDataProviderTest.java
@@ -18,7 +18,24 @@
*/
package org.apache.cxf.rs.security.oauth2.provider;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.rs.security.jose.common.JoseConstants;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jwt.JwtConstants;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.cxf.rs.security.oauth2.common.AccessTokenRegistration;
+import org.apache.cxf.rs.security.oauth2.common.Client;
+import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken;
+
import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class JCacheJWTOAuthDataProviderTest extends
AbstractOAuthDataProviderTest {
@@ -30,4 +47,25 @@ public class JCacheJWTOAuthDataProviderTest extends
AbstractOAuthDataProviderTes
setProvider(provider);
}
+ @Test
+ public void testRefreshedJwtAccessTokenKeepsCertificateBinding() {
+ MessageContext mc = mock(MessageContext.class);
+
when(mc.get(JoseConstants.HEADER_X509_THUMBPRINT_SHA256)).thenReturn("thumbprint");
+ getProvider().setMessageContext(mc);
+
+ Client c = addClient("101", "bob");
+
+ AccessTokenRegistration atr = new AccessTokenRegistration();
+ atr.setClient(c);
+ atr.setApprovedScope(Arrays.asList("a", "refreshToken"));
+ atr.setSubject(c.getResourceOwnerSubject());
+
+ ServerAccessToken at = getProvider().createAccessToken(atr);
+ ServerAccessToken at2 = getProvider().refreshAccessToken(c,
at.getRefreshToken(), Collections.emptyList());
+
+ JwtToken jwt = new
JwsJwtCompactConsumer(at2.getTokenKey()).getJwtToken();
+
assertEquals(Collections.singletonMap(JoseConstants.HEADER_X509_THUMBPRINT_SHA256,
"thumbprint"),
+ jwt.getClaim(JwtConstants.CLAIM_CONFIRMATION));
+ }
+
}