Skip to content

Commit 64a4f9d

Browse files
authored
Merge pull request #57 from PSMRI/logout-check
fix: ensure jwt is not in deny list before further authentication
2 parents bf63df8 + 7e39ef8 commit 64a4f9d

File tree

2 files changed

+83
-19
lines changed

2 files changed

+83
-19
lines changed
Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package com.iemr.tm.utils;
22

3-
import java.security.Key;
4-
import java.util.Date;
53
import java.util.function.Function;
64

5+
import javax.crypto.SecretKey;
6+
7+
import org.springframework.beans.factory.annotation.Autowired;
78
import org.springframework.beans.factory.annotation.Value;
89
import org.springframework.stereotype.Component;
910

1011
import io.jsonwebtoken.Claims;
1112
import io.jsonwebtoken.Jwts;
12-
import io.jsonwebtoken.SignatureAlgorithm;
1313
import io.jsonwebtoken.security.Keys;
1414

1515
@Component
@@ -18,46 +18,70 @@ public class JwtUtil {
1818
@Value("${jwt.secret}")
1919
private String SECRET_KEY;
2020

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

2324
// Generate a key using the secret
24-
private Key getSigningKey() {
25+
private SecretKey getSigningKey() {
2526
if (SECRET_KEY == null || SECRET_KEY.isEmpty()) {
2627
throw new IllegalStateException("JWT secret key is not set in application.properties");
2728
}
2829
return Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
2930
}
3031

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-
4232
// Validate and parse JWT Token
4333
public Claims validateToken(String token) {
4434
try {
45-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
35+
Claims claims = Jwts.parser()
36+
.verifyWith(getSigningKey())
37+
.build()
38+
.parseSignedClaims(token)
39+
.getPayload();
40+
41+
String jti = claims.getId();
42+
43+
// Check if token is denylisted (only if jti exists)
44+
if (jti != null && tokenDenylist.isTokenDenylisted(jti)) {
45+
return null;
46+
}
47+
48+
return claims;
4649
} catch (Exception e) {
4750
return null; // Handle token parsing/validation errors
4851
}
4952
}
5053

54+
// Invalidate a token by adding it to denylist
55+
public void invalidateToken(String token) {
56+
try {
57+
Claims claims = extractAllClaims(token);
58+
if (claims != null && claims.getId() != null) {
59+
// Get remaining time until expiration
60+
long expirationTime = claims.getExpiration().getTime() - System.currentTimeMillis();
61+
if (expirationTime > 0) {
62+
tokenDenylist.addTokenToDenylist(claims.getId(), expirationTime);
63+
}
64+
}
65+
} catch (Exception e) {
66+
// Log error but don't throw - token might be invalid already
67+
throw new RuntimeException("Failed to invalidate token", e);
68+
}
69+
}
70+
5171
public String extractUsername(String token) {
5272
return extractClaim(token, Claims::getSubject);
5373
}
5474

5575
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
5676
final Claims claims = extractAllClaims(token);
57-
return claimsResolver.apply(claims);
77+
return claims != null ? claimsResolver.apply(claims) : null;
5878
}
5979

6080
private Claims extractAllClaims(String token) {
61-
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
81+
return Jwts.parser()
82+
.verifyWith(getSigningKey())
83+
.build()
84+
.parseSignedClaims(token)
85+
.getPayload();
6286
}
6387
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.iemr.tm.utils;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.data.redis.core.RedisTemplate;
5+
import org.springframework.stereotype.Component;
6+
import java.util.concurrent.TimeUnit;
7+
8+
@Component
9+
public class TokenDenylist {
10+
private static final String PREFIX = "denied_";
11+
12+
@Autowired
13+
private RedisTemplate<String, Object> redisTemplate;
14+
15+
public void addTokenToDenylist(String jti, Long expirationTime) {
16+
if (jti != null && expirationTime != null && expirationTime > 0) {
17+
try {
18+
String key = PREFIX + jti;
19+
redisTemplate.opsForValue().set(key, true);
20+
redisTemplate.expire(key, expirationTime, TimeUnit.MILLISECONDS);
21+
} catch (Exception e) {
22+
throw new RuntimeException("Failed to add token to denylist", e);
23+
}
24+
}
25+
}
26+
27+
public boolean isTokenDenylisted(String jti) {
28+
if (jti == null) {
29+
return false;
30+
}
31+
try {
32+
Object value = redisTemplate.opsForValue().get(PREFIX + jti);
33+
return value != null;
34+
} catch (Exception e) {
35+
// If Redis is down, assume token is valid to prevent service disruption
36+
return false;
37+
}
38+
}
39+
40+
}

0 commit comments

Comments
 (0)