From e3eb3a3e89950cf49d8a1f9ecb8530185966e877 Mon Sep 17 00:00:00 2001 From: Mithun James <1007084+drtechie@users.noreply.github.com> Date: Fri, 30 May 2025 17:09:43 +0530 Subject: [PATCH 1/2] fix: ensure jwt is not in deny list before further authentication --- src/main/java/com/iemr/tm/utils/JwtUtil.java | 33 +++++++--- .../java/com/iemr/tm/utils/TokenDenylist.java | 63 +++++++++++++++++++ 2 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/iemr/tm/utils/TokenDenylist.java diff --git a/src/main/java/com/iemr/tm/utils/JwtUtil.java b/src/main/java/com/iemr/tm/utils/JwtUtil.java index 83d98119..4b1be565 100644 --- a/src/main/java/com/iemr/tm/utils/JwtUtil.java +++ b/src/main/java/com/iemr/tm/utils/JwtUtil.java @@ -1,15 +1,16 @@ package com.iemr.tm.utils; -import java.security.Key; import java.util.Date; import java.util.function.Function; +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; @Component @@ -18,10 +19,11 @@ public class JwtUtil { @Value("${jwt.secret}") private String SECRET_KEY; - private static final long EXPIRATION_TIME = 24L * 60 * 60 * 1000; // 1 day in milliseconds + @Autowired + private TokenDenylist tokenDenylist; // Generate a key using the secret - private Key getSigningKey() { + private SecretKey getSigningKey() { if (SECRET_KEY == null || SECRET_KEY.isEmpty()) { throw new IllegalStateException("JWT secret key is not set in application.properties"); } @@ -31,7 +33,20 @@ private Key getSigningKey() { // Validate and parse JWT Token public Claims validateToken(String token) { try { - return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody(); + Claims claims = Jwts.parser() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + + String jti = claims.getId(); + + // Check if token is denylisted + if (tokenDenylist.isTokenDenylisted(jti)) { + return null; + } + + return claims; } catch (Exception e) { return null; // Handle token parsing/validation errors } @@ -43,10 +58,14 @@ public String extractUsername(String token) { public T extractClaim(String token, Function claimsResolver) { final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); + return claims != null ? claimsResolver.apply(claims) : null; } private Claims extractAllClaims(String token) { - return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody(); + return Jwts.parser() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); } } diff --git a/src/main/java/com/iemr/tm/utils/TokenDenylist.java b/src/main/java/com/iemr/tm/utils/TokenDenylist.java new file mode 100644 index 00000000..42ddbacd --- /dev/null +++ b/src/main/java/com/iemr/tm/utils/TokenDenylist.java @@ -0,0 +1,63 @@ +package com.iemr.tm.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +public class TokenDenylist { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private static final String PREFIX = "denied_"; + + @Autowired + private RedisTemplate redisTemplate; + + private String getKey(String jti) { + return PREFIX + jti; + } + + // Add a token's jti to the denylist with expiration time + public void addTokenToDenylist(String jti, Long expirationTime) { + if (jti == null || jti.trim().isEmpty()) { + return; + } + if (expirationTime == null || expirationTime <= 0) { + throw new IllegalArgumentException("Expiration time must be positive"); + } + + try { + String key = getKey(jti); // Use helper method to get the key + redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new RuntimeException("Failed to denylist token", e); + } + } + + // Check if a token's jti is in the denylist + public boolean isTokenDenylisted(String jti) { + if (jti == null || jti.trim().isEmpty()) { + return false; + } + try { + String key = getKey(jti); // Use helper method to get the key + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } catch (Exception e) { + logger.error("Failed to check denylist status for jti: " + jti, e); + // In case of Redis failure, consider the token as not denylisted to avoid blocking all requests + return false; + } + } + + // Remove a token's jti from the denylist (Redis) + public void removeTokenFromDenylist(String jti) { + if (jti != null && !jti.trim().isEmpty()) { + String key = getKey(jti); // Use helper method to get the key + redisTemplate.delete(key); + } + } +} From f332c50a990c90c7a3f553b68908938c5e1a5e0c Mon Sep 17 00:00:00 2001 From: Mithun James <1007084+drtechie@users.noreply.github.com> Date: Fri, 30 May 2025 17:54:39 +0530 Subject: [PATCH 2/2] fix: remove deprecated methods, add null check for jti --- src/main/java/com/iemr/tm/utils/JwtUtil.java | 18 ++++++++---------- .../java/com/iemr/tm/utils/TokenDenylist.java | 8 -------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/iemr/tm/utils/JwtUtil.java b/src/main/java/com/iemr/tm/utils/JwtUtil.java index 4b1be565..2639896e 100644 --- a/src/main/java/com/iemr/tm/utils/JwtUtil.java +++ b/src/main/java/com/iemr/tm/utils/JwtUtil.java @@ -1,8 +1,6 @@ package com.iemr.tm.utils; -import java.util.Date; import java.util.function.Function; - import javax.crypto.SecretKey; import org.springframework.beans.factory.annotation.Autowired; @@ -34,15 +32,15 @@ private SecretKey getSigningKey() { public Claims validateToken(String token) { try { Claims claims = Jwts.parser() - .setSigningKey(getSigningKey()) + .verifyWith(getSigningKey()) .build() - .parseClaimsJws(token) - .getBody(); + .parseSignedClaims(token) + .getPayload(); String jti = claims.getId(); - // Check if token is denylisted - if (tokenDenylist.isTokenDenylisted(jti)) { + // Check if token is denylisted (only if jti exists) + if (jti != null && tokenDenylist.isTokenDenylisted(jti)) { return null; } @@ -63,9 +61,9 @@ public T extractClaim(String token, Function claimsResolver) { private Claims extractAllClaims(String token) { return Jwts.parser() - .setSigningKey(getSigningKey()) + .verifyWith(getSigningKey()) .build() - .parseClaimsJws(token) - .getBody(); + .parseSignedClaims(token) + .getPayload(); } } diff --git a/src/main/java/com/iemr/tm/utils/TokenDenylist.java b/src/main/java/com/iemr/tm/utils/TokenDenylist.java index 42ddbacd..03e19ad3 100644 --- a/src/main/java/com/iemr/tm/utils/TokenDenylist.java +++ b/src/main/java/com/iemr/tm/utils/TokenDenylist.java @@ -52,12 +52,4 @@ public boolean isTokenDenylisted(String jti) { return false; } } - - // Remove a token's jti from the denylist (Redis) - public void removeTokenFromDenylist(String jti) { - if (jti != null && !jti.trim().isEmpty()) { - String key = getKey(jti); // Use helper method to get the key - redisTemplate.delete(key); - } - } }