This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 8c5ea6da4e647ecc7feaa24b4d66b5d36d6f94e5
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
    
    (cherry picked from commit 950c270eb25e9373b7dde7211d608dd20f043dce)
---
 .../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));
+    }
+
 }

Reply via email to