From 86b5607a03b6955d14987e076ee186336ad69d0f Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Wed, 14 May 2025 06:16:14 -0400 Subject: [PATCH] Fix DPoP jkt claim validation during refresh_token grant for public clients Closes gh-2008 --- ...th2RefreshTokenAuthenticationProvider.java | 21 ++++--------------- .../OAuth2RefreshTokenGrantTests.java | 3 +-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java index 8b355e72..a9e3fbab 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java @@ -15,16 +15,12 @@ */ package org.springframework.security.oauth2.server.authorization.authentication; -import java.security.MessageDigest; import java.security.Principal; -import java.security.PublicKey; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import com.nimbusds.jose.jwk.AsymmetricJWK; import com.nimbusds.jose.jwk.JWK; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -292,18 +288,15 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic } private static void verifyDPoPProofPublicKey(Jwt dPoPProof, ClaimAccessor accessTokenClaims) { - PublicKey publicKey = null; + JWK jwk = null; @SuppressWarnings("unchecked") Map jwkJson = (Map) dPoPProof.getHeaders().get("jwk"); try { - JWK jwk = JWK.parse(jwkJson); - if (jwk instanceof AsymmetricJWK) { - publicKey = ((AsymmetricJWK) jwk).toPublicKey(); - } + jwk = JWK.parse(jwkJson); } catch (Exception ignored) { } - if (publicKey == null) { + if (jwk == null) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, "jwk header is missing or invalid.", null); throw new OAuth2AuthenticationException(error); @@ -311,7 +304,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic String jwkThumbprint; try { - jwkThumbprint = computeSHA256(publicKey); + jwkThumbprint = jwk.computeThumbprint().toString(); } catch (Exception ex) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, @@ -335,10 +328,4 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic } } - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java index ce37fb15..186e5cc6 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java @@ -295,9 +295,8 @@ public class OAuth2RefreshTokenGrantTests { OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); Map accessTokenClaims = new HashMap<>(); - PublicKey publicKey = TestJwks.DEFAULT_EC_JWK.toPublicKey(); Map cnfClaim = new HashMap<>(); - cnfClaim.put("jkt", computeSHA256(publicKey)); + cnfClaim.put("jkt", TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); accessTokenClaims.put("cnf", cnfClaim); OAuth2Authorization authorization = TestOAuth2Authorizations .authorization(registeredClient, accessToken, accessTokenClaims)