Skip to content

Commit 2a42ba4

Browse files
authored
Merge pull request #83 from PSMRI/logout-check
fix: ensure jwt is not in deny list before further authentication
2 parents 5485662 + 025205f commit 2a42ba4

2 files changed

Lines changed: 80 additions & 19 deletions

File tree

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package com.iemr.admin.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,34 @@ 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();
40-
}
41-
4231
// Validate and parse JWT Token
4332
public Claims validateToken(String token) {
4433
try {
45-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
34+
Claims claims = Jwts.parser()
35+
.verifyWith(getSigningKey())
36+
.build()
37+
.parseSignedClaims(token)
38+
.getPayload();
39+
40+
String jti = claims.getId();
41+
42+
// Check if token is denylisted (only if jti exists)
43+
if (jti != null && tokenDenylist.isTokenDenylisted(jti)) {
44+
return null;
45+
}
46+
47+
return claims;
4648
} catch (Exception e) {
4749
return null; // Handle token parsing/validation errors
4850
}
@@ -54,10 +56,14 @@ public String extractUsername(String token) {
5456

5557
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
5658
final Claims claims = extractAllClaims(token);
57-
return claimsResolver.apply(claims);
59+
return claims != null ? claimsResolver.apply(claims) : null;
5860
}
5961

6062
private Claims extractAllClaims(String token) {
61-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
63+
return Jwts.parser()
64+
.verifyWith(getSigningKey())
65+
.build()
66+
.parseSignedClaims(token)
67+
.getPayload();
6268
}
6369
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.iemr.admin.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)