diff --git a/src/main/java/com/wipro/fhir/utils/JwtUtil.java b/src/main/java/com/wipro/fhir/utils/JwtUtil.java index 6f22eb7..640070e 100644 --- a/src/main/java/com/wipro/fhir/utils/JwtUtil.java +++ b/src/main/java/com/wipro/fhir/utils/JwtUtil.java @@ -1,15 +1,14 @@ package com.wipro.fhir.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,36 +17,34 @@ 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"); } return Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); } - // Generate JWT Token - public String generateToken(String username, String userId) { - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); - - // Include the userId in the JWT claims - return Jwts.builder().setSubject(username).claim("userId", userId) // Add userId as a claim - .setIssuedAt(now).setExpiration(expiryDate).signWith(getSigningKey(), SignatureAlgorithm.HS256) - .compact(); - } - // Validate and parse JWT Token public Claims validateToken(String token) { try { - // Use the JwtParserBuilder correctly in version 0.12.6 - return Jwts.parser() // Correct method in 0.12.6 to get JwtParserBuilder - .setSigningKey(getSigningKey()) // Set the signing key - .build() // Build the JwtParser - .parseClaimsJws(token) // Parse and validate the token - .getBody(); + Claims claims = Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + + String jti = claims.getId(); + + // Check if token is denylisted (only if jti exists) + if (jti != null && tokenDenylist.isTokenDenylisted(jti)) { + return null; + } + + return claims; } catch (Exception e) { return null; // Handle token parsing/validation errors } @@ -59,10 +56,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() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); } } diff --git a/src/main/java/com/wipro/fhir/utils/TokenDenylist.java b/src/main/java/com/wipro/fhir/utils/TokenDenylist.java new file mode 100644 index 0000000..41e4358 --- /dev/null +++ b/src/main/java/com/wipro/fhir/utils/TokenDenylist.java @@ -0,0 +1,55 @@ +package com.wipro.fhir.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; + } + } +}