-
Notifications
You must be signed in to change notification settings - Fork 45
AMM-1507 | Logic changes in token denylisting in forceLogout API #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
srishtigrp78
merged 15 commits into
PSMRI:develop
from
srishtigrp78:feature/version/upgrade
May 30, 2025
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
6b43897
Merge pull request #32 from PSMRI/develop
srishtigrp78 053c289
Force Logout Jwttoken changes (#210)
ravishanigarapu c34e4c7
Merge pull request #33 from PSMRI/feature/forceLogout
srishtigrp78 dc9c5a5
adding token denylisting changes to forceLogout API
e867131
removing commented code
0661080
remove unnecessary comments
1bca9d5
removing unused values
5db8458
adding code rabbit suggestions
8d5d3fa
applying code rabbit suggestions to code
c0e7bfb
fixing issues highlighted by Mithun
a43afd6
adding sonar quality suggested changes
04ebe64
logic change for calculating expiration time
25e60e6
adding code rabbit suggested changes
9efda6d
removing duplicate line
c4d83c6
removed comment
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,177 @@ | ||
package com.iemr.common.utils; | ||
|
||
import java.util.Date; | ||
import java.util.UUID; | ||
import java.util.function.Function; | ||
import io.jsonwebtoken.*; | ||
import io.jsonwebtoken.security.Keys; | ||
|
||
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.ExpiredJwtException; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
import io.jsonwebtoken.UnsupportedJwtException; | ||
import io.jsonwebtoken.security.Keys; | ||
import io.jsonwebtoken.security.SignatureException; | ||
|
||
import javax.crypto.SecretKey; | ||
import java.util.Date; | ||
import java.util.UUID; | ||
import java.util.function.Function; | ||
|
||
@Component | ||
public class JwtUtil { | ||
|
||
@Value("${jwt.secret}") | ||
private String SECRET_KEY; | ||
|
||
@Value("${jwt.access.expiration}") | ||
private long ACCESS_EXPIRATION_TIME; | ||
|
||
@Value("${jwt.refresh.expiration}") | ||
private long REFRESH_EXPIRATION_TIME; | ||
|
||
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()); | ||
} | ||
|
||
public String generateToken(String username, String userId) { | ||
return buildToken(username, userId, "access", ACCESS_EXPIRATION_TIME); | ||
} | ||
|
||
public String generateRefreshToken(String username, String userId) { | ||
return buildToken(username, userId, "refresh", REFRESH_EXPIRATION_TIME); | ||
} | ||
|
||
private String buildToken(String username, String userId, String tokenType, long expiration) { | ||
return Jwts.builder() | ||
.subject(username) | ||
.claim("userId", userId) | ||
.claim("token_type", tokenType) | ||
.id(UUID.randomUUID().toString()) | ||
.issuedAt(new Date()) | ||
.expiration(new Date(System.currentTimeMillis() + expiration)) | ||
.signWith(getSigningKey()) | ||
.compact(); | ||
} | ||
|
||
public Claims validateToken(String token) { | ||
try { | ||
return Jwts.parser() | ||
.verifyWith(getSigningKey()) | ||
.build() | ||
.parseSignedClaims(token) | ||
.getPayload(); | ||
|
||
} catch (ExpiredJwtException ex) { | ||
// Handle expired token specifically if needed | ||
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) { | ||
// Log specific error types | ||
} | ||
return null; | ||
} | ||
|
||
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { | ||
final Claims claims = getAllClaimsFromToken(token); | ||
return claimsResolver.apply(claims); | ||
} | ||
|
||
public Claims getAllClaimsFromToken(String token) { | ||
return Jwts.parser() | ||
@Value("${jwt.secret}") | ||
private String SECRET_KEY; | ||
|
||
@Value("${jwt.access.expiration}") | ||
private long ACCESS_EXPIRATION_TIME; | ||
|
||
@Value("${jwt.refresh.expiration}") | ||
private long REFRESH_EXPIRATION_TIME; | ||
|
||
@Autowired | ||
private TokenDenylist tokenDenylist; | ||
|
||
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 an access token. | ||
* | ||
* @param username the username of the user | ||
* @param userId the user ID | ||
* @return the generated JWT access token | ||
*/ | ||
public String generateToken(String username, String userId) { | ||
return buildToken(username, userId, "access", ACCESS_EXPIRATION_TIME); | ||
} | ||
|
||
/** | ||
* Generate a refresh token. | ||
* | ||
* @param username the username of the user | ||
* @param userId the user ID | ||
* @return the generated JWT refresh token | ||
*/ | ||
public String generateRefreshToken(String username, String userId) { | ||
return buildToken(username, userId, "refresh", REFRESH_EXPIRATION_TIME); | ||
} | ||
|
||
/** | ||
* Build a JWT token with the specified parameters. | ||
* | ||
* @param username the username of the user | ||
* @param userId the user ID | ||
* @param tokenType the type of the token (access or refresh) | ||
* @param expiration the expiration time of the token in milliseconds | ||
* @return the generated JWT token | ||
*/ | ||
private String buildToken(String username, String userId, String tokenType, long expiration) { | ||
return Jwts.builder() | ||
.subject(username) | ||
.claim("userId", userId) | ||
.claim("token_type", tokenType) | ||
.id(UUID.randomUUID().toString()) | ||
.issuedAt(new Date()) | ||
.expiration(new Date(System.currentTimeMillis() + expiration)) | ||
.signWith(getSigningKey()) | ||
.compact(); | ||
} | ||
|
||
/** | ||
* Validate the JWT token, checking if it is expired and if it's blacklisted | ||
* @param token the JWT token | ||
* @return Claims if valid, null if invalid (expired or denylisted) | ||
*/ | ||
public Claims validateToken(String token) { | ||
// Check if the token is blacklisted (invalidated by force logout) | ||
if (tokenDenylist.isTokenDenylisted(getJtiFromToken(token))) { | ||
return null; // Token is denylisted, so return null | ||
} | ||
|
||
// Check if the token is expired | ||
if (isTokenExpired(token)) { | ||
return null; // Token is expired, so return null | ||
} | ||
|
||
// If token is not blacklisted and not expired, verify the token signature and return claims | ||
try { | ||
return Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload(); | ||
} catch (ExpiredJwtException ex) { | ||
|
||
return null; // Token is expired, so return null | ||
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) { | ||
return null; // Return null for any other JWT-related issue (invalid format, bad signature, etc.) | ||
} | ||
} | ||
|
||
/** | ||
* Check if the JWT token is expired | ||
* @param token the JWT token | ||
* @return true if expired, false otherwise | ||
*/ | ||
private boolean isTokenExpired(String token) { | ||
Date expirationDate = getAllClaimsFromToken(token).getExpiration(); | ||
return expirationDate.before(new Date()); | ||
} | ||
|
||
/** | ||
* Extract claims from the token | ||
* @param token the JWT token | ||
* @return all claims from the token | ||
*/ | ||
public Claims getAllClaimsFromToken(String token) { | ||
return Jwts.parser() | ||
.verifyWith(getSigningKey()) | ||
.build() | ||
.parseSignedClaims(token) | ||
.getPayload(); | ||
} | ||
|
||
|
||
public long getRefreshTokenExpiration() { | ||
return REFRESH_EXPIRATION_TIME; | ||
} | ||
|
||
public String getUserIdFromToken(String token) { | ||
return getAllClaimsFromToken(token).get("userId", String.class); | ||
} | ||
|
||
// Additional helper methods | ||
public String getJtiFromToken(String token) { | ||
return getAllClaimsFromToken(token).getId(); | ||
} | ||
|
||
public String getUsernameFromToken(String token) { | ||
return getAllClaimsFromToken(token).getSubject(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Extract a specific claim from the token using a function | ||
* @param token the JWT token | ||
* @param claimsResolver the function to extract the claim | ||
* @param <T> the type of the claim | ||
* @return the extracted claim | ||
*/ | ||
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { | ||
final Claims claims = getAllClaimsFromToken(token); | ||
return claimsResolver.apply(claims); | ||
} | ||
|
||
/** | ||
* Get the JWT ID (JTI) from the token | ||
* @param token the JWT token | ||
* @return the JWT ID | ||
*/ | ||
public String getJtiFromToken(String token) { | ||
return getAllClaimsFromToken(token).getId(); | ||
} | ||
|
||
/** | ||
* Get the username from the token | ||
* @param token the JWT token | ||
* @return the username | ||
*/ | ||
public String getUsernameFromToken(String token) { | ||
return getAllClaimsFromToken(token).getSubject(); | ||
} | ||
|
||
/** | ||
* Get the user ID from the token | ||
* @param token the JWT token | ||
* @return the user ID | ||
*/ | ||
public String getUserIdFromToken(String token) { | ||
return getAllClaimsFromToken(token).get("userId", String.class); | ||
} | ||
|
||
/** | ||
* Get the expiration time of the refresh token | ||
* @return the expiration time in milliseconds | ||
*/ | ||
public long getRefreshTokenExpiration() { | ||
return REFRESH_EXPIRATION_TIME; | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is not needed again, but okay
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay Mithun, keeping it as it is as of now.