Skip to content

Commit b43e065

Browse files
committed
fix: ensure jwt is not in deny list before further authentication
1 parent c9b95c5 commit b43e065

2 files changed

Lines changed: 95 additions & 17 deletions

File tree

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package com.iemr.inventory.utils;
22

3-
import java.security.Key;
4-
import java.util.Date;
53
import java.util.function.Function;
4+
import javax.crypto.SecretKey;
65

6+
import org.springframework.beans.factory.annotation.Autowired;
77
import org.springframework.beans.factory.annotation.Value;
88
import org.springframework.stereotype.Component;
99

1010
import io.jsonwebtoken.Claims;
1111
import io.jsonwebtoken.Jwts;
12-
import io.jsonwebtoken.SignatureAlgorithm;
1312
import io.jsonwebtoken.security.Keys;
1413

1514
@Component
@@ -18,31 +17,51 @@ public class JwtUtil {
1817
@Value("${jwt.secret}")
1918
private String SECRET_KEY;
2019

21-
private static final long EXPIRATION_TIME = 24L * 60 * 60 * 1000; // 1 day in milliseconds
20+
@Autowired
21+
private TokenDenylist tokenDenylist;
2222

2323
// Generate a key using the secret
24-
private Key getSigningKey() {
24+
private SecretKey getSigningKey() {
2525
if (SECRET_KEY == null || SECRET_KEY.isEmpty()) {
2626
throw new IllegalStateException("JWT secret key is not set in application.properties");
2727
}
2828
return Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
2929
}
3030

31-
// Generate JWT Token
32-
public String generateToken(String username, String userId) {
33-
Date now = new Date();
34-
Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME);
35-
36-
// Include the userId in the JWT claims
37-
return Jwts.builder().setSubject(username).claim("userId", userId) // Add userId as a claim
38-
.setIssuedAt(now).setExpiration(expiryDate).signWith(getSigningKey(), SignatureAlgorithm.HS256)
39-
.compact();
31+
// Invalidate a token by adding it to denylist
32+
public void invalidateToken(String token) {
33+
try {
34+
Claims claims = extractAllClaims(token);
35+
if (claims != null && claims.getId() != null) {
36+
// Get remaining time until expiration
37+
long expirationTime = claims.getExpiration().getTime() - System.currentTimeMillis();
38+
if (expirationTime > 0) {
39+
tokenDenylist.addTokenToDenylist(claims.getId(), expirationTime);
40+
}
41+
}
42+
} catch (Exception e) {
43+
// Log error but don't throw - token might be invalid already
44+
throw new RuntimeException("Failed to invalidate token", e);
45+
}
4046
}
4147

4248
// Validate and parse JWT Token
4349
public Claims validateToken(String token) {
4450
try {
45-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
51+
Claims claims = Jwts.parser()
52+
.verifyWith(getSigningKey())
53+
.build()
54+
.parseSignedClaims(token)
55+
.getPayload();
56+
57+
String jti = claims.getId();
58+
59+
// Check if token is denylisted (only if jti exists)
60+
if (jti != null && tokenDenylist.isTokenDenylisted(jti)) {
61+
return null;
62+
}
63+
64+
return claims;
4665
} catch (Exception e) {
4766
return null; // Handle token parsing/validation errors
4867
}
@@ -54,10 +73,14 @@ public String extractUsername(String token) {
5473

5574
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
5675
final Claims claims = extractAllClaims(token);
57-
return claimsResolver.apply(claims);
76+
return claims != null ? claimsResolver.apply(claims) : null;
5877
}
5978

6079
private Claims extractAllClaims(String token) {
61-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
80+
return Jwts.parser()
81+
.verifyWith(getSigningKey())
82+
.build()
83+
.parseSignedClaims(token)
84+
.getPayload();
6285
}
6386
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.iemr.inventory.utils;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.data.redis.core.RedisTemplate;
7+
import org.springframework.stereotype.Component;
8+
9+
import java.util.concurrent.TimeUnit;
10+
11+
@Component
12+
public class TokenDenylist {
13+
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
14+
15+
private static final String PREFIX = "denied_";
16+
17+
@Autowired
18+
private RedisTemplate<String, Object> redisTemplate;
19+
20+
private String getKey(String jti) {
21+
return PREFIX + jti;
22+
}
23+
24+
// Add a token's jti to the denylist with expiration time
25+
public void addTokenToDenylist(String jti, Long expirationTime) {
26+
if (jti == null || jti.trim().isEmpty()) {
27+
return;
28+
}
29+
if (expirationTime == null || expirationTime <= 0) {
30+
throw new IllegalArgumentException("Expiration time must be positive");
31+
}
32+
33+
try {
34+
String key = getKey(jti); // Use helper method to get the key
35+
redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS);
36+
} catch (Exception e) {
37+
throw new RuntimeException("Failed to denylist token", e);
38+
}
39+
}
40+
41+
// Check if a token's jti is in the denylist
42+
public boolean isTokenDenylisted(String jti) {
43+
if (jti == null || jti.trim().isEmpty()) {
44+
return false;
45+
}
46+
try {
47+
String key = getKey(jti); // Use helper method to get the key
48+
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
49+
} catch (Exception e) {
50+
logger.error("Failed to check denylist status for jti: " + jti, e);
51+
// In case of Redis failure, consider the token as not denylisted to avoid blocking all requests
52+
return false;
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)