-
Notifications
You must be signed in to change notification settings - Fork 40
Enhance JWT Handling: Return Token in Response for Mobile, Add Secure Refresh API, and Update Middleware #181
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
Changes from all commits
75e5f18
f33b2ae
f864587
dbcc0ab
067bf71
a4236c2
adba41e
5f382bf
9a94bee
d288ed1
08fd1b8
b807305
cd4a707
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,17 +21,14 @@ | |
*/ | ||
package com.iemr.common.controller.users; | ||
|
||
import java.security.NoSuchAlgorithmException; | ||
import java.security.spec.InvalidKeySpecException; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.*; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import javax.ws.rs.core.MediaType; | ||
|
||
import com.iemr.common.utils.UserAgentUtil; | ||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import org.json.JSONArray; | ||
import org.json.JSONObject; | ||
import org.slf4j.Logger; | ||
|
@@ -40,20 +37,13 @@ | |
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.CrossOrigin; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParser; | ||
import com.iemr.common.config.encryption.SecurePassword; | ||
import com.iemr.common.data.directory.Directory; | ||
import com.iemr.common.data.users.LoginSecurityQuestions; | ||
import com.iemr.common.data.users.M_Role; | ||
import com.iemr.common.data.users.ServiceRoleScreenMapping; | ||
|
@@ -63,10 +53,8 @@ | |
import com.iemr.common.model.user.ChangePasswordModel; | ||
import com.iemr.common.model.user.ForceLogoutRequestModel; | ||
import com.iemr.common.model.user.LoginRequestModel; | ||
import com.iemr.common.model.user.LoginResponseModel; | ||
import com.iemr.common.service.users.IEMRAdminUserService; | ||
import com.iemr.common.utils.CookieUtil; | ||
import com.iemr.common.utils.JwtAuthenticationUtil; | ||
import com.iemr.common.utils.JwtUtil; | ||
import com.iemr.common.utils.encryption.AESUtil; | ||
import com.iemr.common.utils.exception.IEMRException; | ||
|
@@ -94,8 +82,6 @@ public class IEMRAdminController { | |
@Autowired | ||
private CookieUtil cookieUtil; | ||
@Autowired | ||
private JwtAuthenticationUtil jwtAuthenticationUtil; | ||
@Autowired | ||
private RedisTemplate<String, Object> redisTemplate; | ||
|
||
private AESUtil aesUtil; | ||
|
@@ -164,21 +150,39 @@ public String userAuthenticate( | |
} else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) { | ||
deleteSessionObject(m_User.getUserName().trim().toLowerCase()); | ||
} | ||
|
||
String jwtToken = null; | ||
String refreshToken = null; | ||
boolean isMobile = false; | ||
if (mUser.size() == 1) { | ||
String Jwttoken = jwtUtil.generateToken(m_User.getUserName(), mUser.get(0).getUserID().toString()); | ||
logger.info("jwt token is:" + Jwttoken); | ||
jwtToken = jwtUtil.generateToken(m_User.getUserName(), mUser.get(0).getUserID().toString()); | ||
|
||
User user = new User(); // Assuming the Users class exists | ||
user.setUserID(mUser.get(0).getUserID()); | ||
user.setUserName(mUser.get(0).getUserName()); | ||
|
||
String redisKey = "user_" + mUser.get(0).getUserID(); // Use user ID to create a unique key | ||
|
||
// Store the user in Redis (set a TTL of 30 minutes) | ||
redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); | ||
String userAgent = request.getHeader("User-Agent"); | ||
isMobile = UserAgentUtil.isMobileDevice(userAgent); | ||
logger.info("UserAgentUtil isMobile : " + isMobile); | ||
|
||
if (isMobile) { | ||
refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); | ||
logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); | ||
String jti = jwtUtil.getJtiFromToken(refreshToken); | ||
redisTemplate.opsForValue().set( | ||
"refresh:" + jti, | ||
user.getUserID().toString(), | ||
jwtUtil.getRefreshTokenExpiration(), | ||
TimeUnit.MILLISECONDS | ||
); | ||
Comment on lines
+172
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainDocument the refresh token expiration strategy. The code uses 🌐 Web query:
💡 Result: JWT refresh token expiration times vary based on security requirements and system design, but common configurations range from 7 days to 90 days, with some systems allowing configurations up to 1 year. Here's a breakdown of typical practices: Common Expiration Periods
Citations:
Document JWT Refresh Token Expiration Strategy Inline At There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. already added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed:
Length of output: 581 @kevalkanp1011 I see that you've configured the refresh token expiration in application.properties ( 🧬 Code Graph Analysis ResultsRelevant Code Snippets1. UserAgentUtil.javaFilename: public class UserAgentUtil {
public static boolean isMobileDevice(String userAgent) {
if (userAgent == null) return false;
String lowerUA = userAgent.toLowerCase();
return lowerUA.contains("mobile") || lowerUA.contains("android") || lowerUA.contains("iphone");
}
} Description: This utility class contains a method to check if the user agent string corresponds to a mobile device. It returns 2. IEMRAdminController.java (userAuthenticate method)Filename: @CrossOrigin()
@Operation(summary = "User authentication")
@RequestMapping(value = "/userAuthenticate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON)
public String userAuthenticate(
@Param(value = "\"{\\\"userName\\\":\\\"String\\\",\\\"password\\\":\\\"String\\\"}\"") @RequestBody LoginRequestModel m_User,
HttpServletRequest request, HttpServletResponse httpResponse) {
OutputResponse response = new OutputResponse();
logger.info("userAuthenticate request - " + m_User + " " + m_User.getUserName() + " " + m_User.getPassword());
try {
String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword());
logger.info("decryptPassword : " + decryptPassword);
List<User> mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword);
// Additional logic for authentication...
} catch (Exception e) {
logger.error("userAuthenticate failed with error " + e.getMessage(), e);
response.setError(e);
}
logger.info("userAuthenticate response " + response.toString());
return response.toString();
} Description: This method handles user authentication. It decrypts the provided password, checks the user credentials, and logs the authentication process. It returns a response indicating the success or failure of the authentication. 3. IEMRAdminController.java (userAuthenticateNew method)Filename: @CrossOrigin()
@Operation(summary = "New user authentication")
@RequestMapping(value = "/userAuthenticateNew", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON)
public String userAuthenticateNew(
@Param(value = "\"{\\\"userName\\\":\\\"String\\\",\\\"password\\\":\\\"String\\\"}\"") @RequestBody String jsonRequest) {
OutputResponse response = new OutputResponse();
try {
User m_user = inputMapper.gson().fromJson(jsonRequest, User.class);
response.setResponse("hello.....");
} catch (Exception e) {
response.setError(e);
}
return response.toString();
} Description: This method is designed for new user authentication. It parses the incoming JSON request to extract user credentials and sets a response. The implementation is basic and primarily serves as a placeholder. 4. IEMRAdminController.java (logOutUserFromConcurrentSession method)Filename: @CrossOrigin()
@Operation(summary = "Log out user from concurrent session")
@RequestMapping(value = "/logOutUserFromConcurrentSession", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON)
public String logOutUserFromConcurrentSession(
@Param(value = "\"{\\\"userName\\\":\\\"String\\\"}\"") @RequestBody LoginRequestModel m_User,
HttpServletRequest request) {
OutputResponse response = new OutputResponse();
logger.info("logOutUserFromConcurrentSession request - " + m_User);
try {
if (m_User != null && m_User.getUserName() != null) {
List<User> mUsers = iemrAdminUserServiceImpl.userExitsCheck(m_User.getUserName());
// Additional logic for logging out...
}
} catch (Exception e) {
logger.error("logOutUserFromConcurrentSession failed with error " + e.getMessage(), e);
response.setError(e);
}
logger.info("logOutUserFromConcurrentSession response " + response.toString());
return response.toString();
} Description: This method logs out a user from a concurrent session. It checks if the user exists and performs the necessary actions to log them out, handling any exceptions that may occur during the process. 5. IEMRAdminController.java (refreshToken method)Filename: @Operation(summary = "generating a auth token with the refreshToken.")
@RequestMapping(value = "/refreshToken", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<?> refreshToken(@RequestBody Map<String, String> request) {
String refreshToken = request.get("refreshToken");
// Logic for refreshing token...
} Description: This method handles the generation of a new authentication token using a provided refresh token. It validates the refresh token and generates a new JWT if valid. These snippets provide context on user authentication and session management, which may relate to the user's comment about something being "already added." |
||
} else { | ||
cookieUtil.addJwtTokenToCookie(jwtToken, httpResponse, request); | ||
} | ||
|
||
String redisKey = "user_" + mUser.get(0).getUserID(); // Use user ID to create a unique key | ||
|
||
// Set Jwttoken in the response cookie | ||
cookieUtil.addJwtTokenToCookie(Jwttoken, httpResponse, request); | ||
// Store the user in Redis (set a TTL of 30 minutes) | ||
redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); | ||
|
||
createUserMapping(mUser.get(0), resMap, serviceRoleMultiMap, serviceRoleMap, serviceRoleList, | ||
previlegeObj); | ||
|
@@ -199,6 +203,13 @@ public String userAuthenticate( | |
} | ||
responseObj = iemrAdminUserServiceImpl.generateKeyAndValidateIP(responseObj, remoteAddress, | ||
request.getRemoteHost()); | ||
|
||
// Add tokens to response for mobile | ||
if (isMobile && !mUser.isEmpty()) { | ||
responseObj.put("jwtToken", jwtToken); | ||
responseObj.put("refreshToken", refreshToken); | ||
} | ||
|
||
response.setResponse(responseObj.toString()); | ||
} catch (Exception e) { | ||
logger.error("userAuthenticate failed with error " + e.getMessage(), e); | ||
|
@@ -208,6 +219,68 @@ public String userAuthenticate( | |
return response.toString(); | ||
} | ||
|
||
@Operation(summary = "generating a auth token with the refreshToken.") | ||
@RequestMapping(value = "/refreshToken", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON) | ||
public ResponseEntity<?> refreshToken(@RequestBody Map<String, String> request) { | ||
String refreshToken = request.get("refreshToken"); | ||
|
||
try { | ||
if (jwtUtil.validateToken(refreshToken) == null) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token"); | ||
} | ||
|
||
Claims claims = jwtUtil.getAllClaimsFromToken(refreshToken); | ||
|
||
// Verify token type | ||
if (!"refresh".equals(claims.get("token_type", String.class))) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token type"); | ||
} | ||
|
||
// Check revocation using JTI | ||
String jti = claims.getId(); | ||
if (!redisTemplate.hasKey("refresh:" + jti)) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token revoked"); | ||
} | ||
|
||
// Get user details | ||
// Get user details | ||
String userId = claims.get("userId", String.class); | ||
User user = iemrAdminUserServiceImpl.getUserById(Long.parseLong(userId)); | ||
|
||
// Validate that the user still exists and is active | ||
if (user == null) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found"); | ||
} | ||
|
||
if (user.getM_status() == null || !"Active".equalsIgnoreCase(user.getM_status().getStatus())) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User account is inactive"); | ||
} | ||
// Generate new tokens | ||
String newJwt = jwtUtil.generateToken(user.getUserName(), userId); | ||
|
||
Map<String, String> tokens = new HashMap<>(); | ||
tokens.put("jwtToken", newJwt); | ||
|
||
// Generate and store a new refresh token (token rotation) | ||
String newRefreshToken = jwtUtil.generateRefreshToken(user.getUserName(), userId); | ||
String newJti = jwtUtil.getJtiFromToken(newRefreshToken); | ||
redisTemplate.opsForValue().set( | ||
"refresh:" + newJti, | ||
userId, | ||
jwtUtil.getRefreshTokenExpiration(), | ||
TimeUnit.MILLISECONDS | ||
); | ||
tokens.put("refreshToken", newRefreshToken); | ||
|
||
return ResponseEntity.ok(tokens); | ||
kevalkanp1011 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} catch (ExpiredJwtException ex) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token expired"); | ||
} catch (Exception e) { | ||
logger.error("Refresh failed: ", e); | ||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Token refresh failed"); | ||
} | ||
} | ||
|
||
@CrossOrigin() | ||
@Operation(summary = "Log out user from concurrent session") | ||
@RequestMapping(value = "/logOutUserFromConcurrentSession", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON) | ||
|
@@ -740,7 +813,7 @@ public String userLogout(HttpServletRequest request) { | |
|
||
/** | ||
* | ||
* @param request | ||
* @param key | ||
* @return | ||
*/ | ||
private void deleteSessionObjectByGettingSessionDetails(String key) { | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,68 +1,101 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package com.iemr.common.utils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.security.Key; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.Date; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import java.util.function.Function; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.SignatureAlgorithm; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import io.jsonwebtoken.MalformedJwtException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import io.jsonwebtoken.UnsupportedJwtException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import io.jsonwebtoken.security.Keys; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import io.jsonwebtoken.security.SignatureException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import javax.crypto.SecretKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public class JwtUtil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Value("${jwt.secret}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private String SECRET_KEY; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private static final long EXPIRATION_TIME = 24L * 60 * 60 * 1000; // 1 day in milliseconds | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Value("${jwt.access.expiration}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these new env variabes. If yes, please add them to application properties There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. currently its configured to change from environment specific .properties files. bcz jwt-secret was listed in these files . so i put just below of it to easily find out |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private long ACCESS_EXPIRATION_TIME; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Value("${jwt.refresh.expiration}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private long REFRESH_EXPIRATION_TIME; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return buildToken(username, userId, "access", ACCESS_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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return null; // Handle token parsing/validation errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
59
to
73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve exception handling in validateToken method While the updated validation logic using the new JWT API is good, the exception handling needs improvement. The current implementation has empty catch blocks that silently ignore errors. This will make debugging difficult and hide potential security issues. public Claims validateToken(String token) {
try {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
} catch (ExpiredJwtException ex) {
- // Handle expired token specifically if needed
+ // Log the expired token with specific error code
+ logger.warn("JWT token has expired: {}", ex.getMessage());
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) {
- // Log specific error types
+ // Log the specific error type for security monitoring
+ logger.error("JWT validation error: {} - {}", ex.getClass().getName(), ex.getMessage());
}
return null;
} Consider adding a logging mechanism (e.g., SLF4J) to record these exceptions for security monitoring and debugging purposes. 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public String extractUsername(String token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return extractClaim(token, Claims::getSubject); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
final Claims claims = getAllClaimsFromToken(token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return claimsResolver.apply(claims); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+75
to
78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add exception handling to token claim extraction methods The claim extraction methods are well-structured, but they lack exception handling. When these methods are called with invalid tokens, they will throw uncaught exceptions that could crash the application. Add proper exception handling: public Claims getAllClaimsFromToken(String token) {
+ try {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
+ } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException |
+ SignatureException | IllegalArgumentException ex) {
+ // Log the specific error
+ logger.error("Error extracting claims from token: {}", ex.getMessage());
+ return null;
+ }
} The same exception handling should be applied to Also applies to: 80-86 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
final Claims claims = extractAllClaims(token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return claimsResolver.apply(claims); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public Claims getAllClaimsFromToken(String token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Jwts.parser() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.verifyWith(getSigningKey()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.parseSignedClaims(token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.getPayload(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public long getRefreshTokenExpiration() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return REFRESH_EXPIRATION_TIME; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Additional helper methods | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public String getJtiFromToken(String token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return getAllClaimsFromToken(token).getId(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+94
to
96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add exception handling to token extraction helper methods The helper methods for extracting specific claims from tokens should include exception handling. These methods directly call public String getJtiFromToken(String token) {
+ Claims claims = getAllClaimsFromToken(token);
+ return claims != null ? claims.getId() : null;
- return getAllClaimsFromToken(token).getId();
}
public String getUsernameFromToken(String token) {
+ Claims claims = getAllClaimsFromToken(token);
+ return claims != null ? claims.getSubject() : null;
- return getAllClaimsFromToken(token).getSubject();
} Also applies to: 98-100 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private Claims extractAllClaims(String token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public String getUsernameFromToken(String token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return getAllClaimsFromToken(token).getSubject(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
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.
What's the expiry of refresh token? Are we storing it in Redis?
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.
jwt.access.expiration=900000 (in ms)
jwt.refresh.expiration=604800000(in ms)
can be modified from .properties files