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 @@ protected ServerAccessToken doCreateAccessToken(AccessTokenRegistration atReg) { 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 @@ protected ServerAccessToken doCreateAccessToken(AccessTokenRegistration atReg) { at.getExtraProperties().put(JoseConstants.HEADER_X509_THUMBPRINT_SHA256, certCnf); } } - - if (isUseJwtFormatForAccessTokens()) { - convertToJWTAccessToken(at); - } - - return at; } //CHECKSTYLE:OFF @@ -475,6 +479,8 @@ protected ServerAccessToken doRefreshAccessToken(Client client, 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.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.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 @@ public void testCrossClientRefreshTokenRejectedWhenRecycleDisabled() { 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 void setUp() throws Exception { 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)); + } + }