From 75e5f18a24ecfcdd0a1f0bfe1228d5c2e524b6d2 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Mon, 31 Mar 2025 11:53:52 +0530 Subject: [PATCH 01/11] Generate a JWT token in the response body for mobile users instead of setting a cookie, Implement refresh tokens with a long expiry for session management, Create a new API to refresh JWT tokens securely, Modify middleware to handle both cookies (for web) and Authorization headers (for mobile). --- .../controller/users/IEMRAdminController.java | 119 +++++++++++++----- .../service/users/IEMRAdminUserService.java | 2 +- .../utils/JwtUserIdValidationFilter.java | 27 ++-- .../java/com/iemr/common/utils/JwtUtil.java | 86 +++++++++---- .../com/iemr/common/utils/UserAgentUtil.java | 9 ++ .../utils/http/HTTPRequestInterceptor.java | 3 +- 6 files changed, 178 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/iemr/common/utils/UserAgentUtil.java diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index c05436e1..246b9713 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -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 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.info("Generated refresh token: {}", refreshToken); + String jti = jwtUtil.getJtiFromToken(refreshToken); + redisTemplate.opsForValue().set( + "refresh:" + jti, + user.getUserID().toString(), + jwtUtil.getRefreshTokenExpiration(), + TimeUnit.MILLISECONDS + ); + } 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,58 @@ public String userAuthenticate( return response.toString(); } + @CrossOrigin() + @Operation(summary = "generates a refresh token") + @RequestMapping(value = "/refreshToken", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON) + public ResponseEntity refreshToken(@RequestBody Map request) { + String refreshToken = request.get("refreshToken"); + + try { + if (jwtUtil.validateToken(refreshToken) == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token"); + } + + try { + Claims claims = jwtUtil.getAllClaimsFromToken(refreshToken); + logger.info("refresh token api claims are:" + claims); + } catch (Exception e) { + logger.info("refresh token api claims exception:" + e.getMessage()); + throw new RuntimeException(e); + } + + + 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 + String userId = claims.get("userId", String.class); + User user = iemrAdminUserServiceImpl.getUserById(Long.parseLong(userId)); + + // Generate new tokens + String newJwt = jwtUtil.generateToken(user.getUserName(), userId); + + Map tokens = new HashMap<>(); + tokens.put("jwtToken", newJwt); + + return ResponseEntity.ok(tokens); + } 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 +803,7 @@ public String userLogout(HttpServletRequest request) { /** * - * @param request + * @param key * @return */ private void deleteSessionObjectByGettingSessionDetails(String key) { diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java index 58ed2b00..3f1d8068 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java @@ -116,7 +116,7 @@ public List getUserServiceRoleMappingForProvider(Integ String generateTransactionIdForPasswordChange(User user) throws Exception; - + User getUserById(Long userId) throws IEMRException; diff --git a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java index 2eb2ee2e..b83f4873 100644 --- a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java @@ -52,13 +52,9 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo String jwtTokenFromHeader = request.getHeader("Jwttoken"); logger.info("JWT token from header: "); - // Skip login and public endpoints - if (path.equals(contextPath + "/user/userAuthenticate") - || path.equalsIgnoreCase(contextPath + "/user/logOutUserFromConcurrentSession") - || path.startsWith(contextPath + "/swagger-ui") - || path.startsWith(contextPath + "/v3/api-docs") - || path.startsWith(contextPath + "/public")) { - logger.info("Skipping filter for path: " + path); + // Skip authentication for public endpoints + if (shouldSkipAuthentication(path, contextPath)) { + logger.info("Skipping filter for path: {}", path); filterChain.doFilter(servletRequest, servletResponse); return; } @@ -71,17 +67,18 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo // Determine which token (cookie or header) to validate String jwtToken = jwtTokenFromCookie != null ? jwtTokenFromCookie : jwtTokenFromHeader; if (jwtToken == null) { + logger.error("JWT token not found in cookies or headers"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT token not found in cookies or headers"); return; } // Validate JWT token and userId - boolean isValid = jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken); - - if (isValid) { + if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken)) { // If token is valid, allow the request to proceed + logger.info("Valid JWT token"); filterChain.doFilter(servletRequest, servletResponse); } else { + logger.error("Invalid JWT token"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); } } catch (Exception e) { @@ -90,6 +87,16 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } + private boolean shouldSkipAuthentication(String path, String contextPath) { + return path.equals(contextPath + "/user/userAuthenticate") + || path.equalsIgnoreCase(contextPath + "/user/logOutUserFromConcurrentSession") + || path.startsWith(contextPath + "/swagger-ui") + || path.startsWith(contextPath + "/v3/api-docs") + || path.startsWith(contextPath + "/public") + || path.equals(contextPath + "/user/refreshToken") + ; + } + private String getJwtTokenFromCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index a6e8b942..faf3b89d 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -1,16 +1,21 @@ 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 { @@ -18,51 +23,78 @@ 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}") + 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; } - public String extractUsername(String token) { - return extractClaim(token, Claims::getSubject); + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); } - public T extractClaim(String token, Function 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(); } - private Claims extractAllClaims(String token) { - return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody(); + public String getUsernameFromToken(String token) { + return getAllClaimsFromToken(token).getSubject(); } } \ No newline at end of file diff --git a/src/main/java/com/iemr/common/utils/UserAgentUtil.java b/src/main/java/com/iemr/common/utils/UserAgentUtil.java new file mode 100644 index 00000000..e6b0dbce --- /dev/null +++ b/src/main/java/com/iemr/common/utils/UserAgentUtil.java @@ -0,0 +1,9 @@ +package com.iemr.common.utils; + +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"); + } +} diff --git a/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java index a31b2a2b..b8359fe6 100644 --- a/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/common/utils/http/HTTPRequestInterceptor.java @@ -101,13 +101,12 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons case "api-docs": case "updateBenCallIdsInPhoneBlock": case "userAuthenticateByEncryption": - case "sendOTP": case "validateOTP": case "resendOTP": case "validateSecurityQuestionAndAnswer": case "logOutUserFromConcurrentSession": - + case "refreshToken": break; case "error": status = false; From f33b2ae23897647723d566fb85127c7e68adebb3 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Mon, 31 Mar 2025 12:11:22 +0530 Subject: [PATCH 02/11] unnecessary code removed --- .../common/controller/users/IEMRAdminController.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 246b9713..9cf8a515 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -230,15 +230,6 @@ public ResponseEntity refreshToken(@RequestBody Map request) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token"); } - try { - Claims claims = jwtUtil.getAllClaimsFromToken(refreshToken); - logger.info("refresh token api claims are:" + claims); - } catch (Exception e) { - logger.info("refresh token api claims exception:" + e.getMessage()); - throw new RuntimeException(e); - } - - Claims claims = jwtUtil.getAllClaimsFromToken(refreshToken); // Verify token type From f864587161f706c05ee628ac9e9ffd00a7c1b1bf Mon Sep 17 00:00:00 2001 From: Keval Kanpariya <74661970+kevalkanp1011@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:47:49 +0530 Subject: [PATCH 03/11] Update src/main/java/com/iemr/common/controller/users/IEMRAdminController.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../com/iemr/common/controller/users/IEMRAdminController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 9cf8a515..8832cb7c 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -167,7 +167,7 @@ public String userAuthenticate( if (isMobile) { refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); - logger.info("Generated refresh token: {}", refreshToken); + logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); String jti = jwtUtil.getJtiFromToken(refreshToken); redisTemplate.opsForValue().set( "refresh:" + jti, From dbcc0abc61dbdd2dc6dcbba8cedd4265d5408a6d Mon Sep 17 00:00:00 2001 From: Keval Kanpariya <74661970+kevalkanp1011@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:56:04 +0530 Subject: [PATCH 04/11] Update src/main/java/com/iemr/common/controller/users/IEMRAdminController.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../common/controller/users/IEMRAdminController.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 8832cb7c..0ba3f76b 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -243,10 +243,19 @@ public ResponseEntity refreshToken(@RequestBody Map request) 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); From 067bf718fcaa44d5708d66919c6d294a05e28960 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Mon, 31 Mar 2025 20:07:45 +0530 Subject: [PATCH 05/11] review changes: added doc for refresh token access and added jwt.access.expiration and jwt.refresh.expiration in common_example.properties file. --- src/main/environment/common_example.properties | 2 ++ .../controller/users/IEMRAdminController.java | 3 +-- src/main/java/com/iemr/common/utils/JwtUtil.java | 13 +++++++++++++ src/main/resources/application.properties | 4 +++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 9a90154d..5fd7cc99 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -169,6 +169,8 @@ eausadhaAuthorization= spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true jwt.secret= +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 fileBasePath =/Doc diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 9cf8a515..de4ef3b8 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -219,8 +219,7 @@ public String userAuthenticate( return response.toString(); } - @CrossOrigin() - @Operation(summary = "generates a refresh token") + @Operation(summary = "generating a auth token with the refreshToken.") @RequestMapping(value = "/refreshToken", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON) public ResponseEntity refreshToken(@RequestBody Map request) { String refreshToken = request.get("refreshToken"); diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index faf3b89d..03d2a9b1 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -85,6 +85,19 @@ public Claims getAllClaimsFromToken(String token) { .getPayload(); } + /** + * Retrieves the refresh token expiration time. + * + * The refresh token expiration time determines how long a refresh token remains valid. + * A longer expiration time allows users to stay logged in without frequently re-authenticating. + * + * Security & Business Considerations: + * - A longer expiration (e.g., 7 days or more) improves user experience but may pose security risks if tokens are leaked. + * - A shorter expiration (e.g., 1 hour) enhances security but may require users to log in more frequently. + * - This duration is configurable and can be overridden in the environment specific application properties file. + * + * @return The expiration time in milliseconds. + */ public long getRefreshTokenExpiration() { return REFRESH_EXPIRATION_TIME; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d645f273..9ff13bfa 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -178,4 +178,6 @@ everwellDataSyncDuration = 15 quality-Audit-PageSize=5 ## max no of failed login attempt -failedLoginAttempt=5 +failedLoginAttempt= + + From adba41e52f6e017e70c311948e25466691557597 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya <74661970+kevalkanp1011@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:49:13 +0530 Subject: [PATCH 06/11] Update src/main/java/com/iemr/common/controller/users/IEMRAdminController.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../controller/users/IEMRAdminController.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 3f33a8fb..28cb7227 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -261,6 +261,20 @@ public ResponseEntity refreshToken(@RequestBody Map request) Map tokens = new HashMap<>(); tokens.put("jwtToken", newJwt); + Map 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); } catch (ExpiredJwtException ex) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token expired"); From 5f382bf48b214476b229e83b15dacf114af3b099 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Tue, 1 Apr 2025 09:57:11 +0530 Subject: [PATCH 07/11] review changes --- src/main/environment/common_ci.properties | 2 ++ src/main/environment/common_dev.properties | 2 ++ src/main/environment/common_test.properties | 2 ++ src/main/environment/common_uat.properties | 2 ++ src/main/resources/application.properties | 4 +--- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 1f82e9ea..9a5362ac 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -141,6 +141,8 @@ spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true jwt.secret=@env.JWT_SECRET_KEY@ +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 #ELK logging file name diff --git a/src/main/environment/common_dev.properties b/src/main/environment/common_dev.properties index 597a8bf6..25dabd6f 100644 --- a/src/main/environment/common_dev.properties +++ b/src/main/environment/common_dev.properties @@ -171,6 +171,8 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list?page=PageNumber¤tpage=1 diff --git a/src/main/environment/common_test.properties b/src/main/environment/common_test.properties index df9007ee..a13ae5fd 100644 --- a/src/main/environment/common_test.properties +++ b/src/main/environment/common_test.properties @@ -172,6 +172,8 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 ##grievance API call diff --git a/src/main/environment/common_uat.properties b/src/main/environment/common_uat.properties index 92c55f4a..637047ea 100644 --- a/src/main/environment/common_uat.properties +++ b/src/main/environment/common_uat.properties @@ -144,6 +144,8 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list?page=PageNumber¤tpage=1 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9ff13bfa..d645f273 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -178,6 +178,4 @@ everwellDataSyncDuration = 15 quality-Audit-PageSize=5 ## max no of failed login attempt -failedLoginAttempt= - - +failedLoginAttempt=5 From d288ed1518536c794c63ad711b1c37dd0bb8112b Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Tue, 1 Apr 2025 10:00:07 +0530 Subject: [PATCH 08/11] review changes --- src/main/environment/common_ci.properties | 2 -- src/main/environment/common_dev.properties | 2 -- src/main/environment/common_example.properties | 2 -- src/main/environment/common_test.properties | 2 -- src/main/environment/common_uat.properties | 2 -- src/main/resources/application.properties | 4 ++++ 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 9a5362ac..1f82e9ea 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -141,8 +141,6 @@ spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true jwt.secret=@env.JWT_SECRET_KEY@ -jwt.access.expiration=900000 -jwt.refresh.expiration=604800000 #ELK logging file name diff --git a/src/main/environment/common_dev.properties b/src/main/environment/common_dev.properties index 25dabd6f..597a8bf6 100644 --- a/src/main/environment/common_dev.properties +++ b/src/main/environment/common_dev.properties @@ -171,8 +171,6 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= -jwt.access.expiration=900000 -jwt.refresh.expiration=604800000 ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list?page=PageNumber¤tpage=1 diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 5fd7cc99..9a90154d 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -169,8 +169,6 @@ eausadhaAuthorization= spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true jwt.secret= -jwt.access.expiration=900000 -jwt.refresh.expiration=604800000 fileBasePath =/Doc diff --git a/src/main/environment/common_test.properties b/src/main/environment/common_test.properties index a13ae5fd..df9007ee 100644 --- a/src/main/environment/common_test.properties +++ b/src/main/environment/common_test.properties @@ -172,8 +172,6 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= -jwt.access.expiration=900000 -jwt.refresh.expiration=604800000 ##grievance API call diff --git a/src/main/environment/common_uat.properties b/src/main/environment/common_uat.properties index 637047ea..92c55f4a 100644 --- a/src/main/environment/common_uat.properties +++ b/src/main/environment/common_uat.properties @@ -144,8 +144,6 @@ spring.main.allow-circular-references=true fileBasePath =/Doc jwt.secret= -jwt.access.expiration=900000 -jwt.refresh.expiration=604800000 ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list?page=PageNumber¤tpage=1 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d645f273..9418e973 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -179,3 +179,7 @@ quality-Audit-PageSize=5 ## max no of failed login attempt failedLoginAttempt=5 + +#Jwt Token configuration +jwt.access.expiration=900000 +jwt.refresh.expiration=604800000 From 08fd1b823f514ef9f6f6273154be97687746a276 Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Fri, 4 Apr 2025 12:56:34 +0530 Subject: [PATCH 09/11] review changes --- pom.xml | 1 + .../com/iemr/common/controller/users/IEMRAdminController.java | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index db199065..cc723da8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ common-API Piramal - Helpline: 1097 Module API + Piramal - Helpline: 1097 Module API org.springframework.boot diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 28cb7227..e26f15dd 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -258,9 +258,6 @@ public ResponseEntity refreshToken(@RequestBody Map request) // Generate new tokens String newJwt = jwtUtil.generateToken(user.getUserName(), userId); - Map tokens = new HashMap<>(); - tokens.put("jwtToken", newJwt); - Map tokens = new HashMap<>(); tokens.put("jwtToken", newJwt); From b807305b488cc5a69e0762905ac10c8a499f236e Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Sat, 5 Apr 2025 15:46:20 +0530 Subject: [PATCH 10/11] duplicate description removed --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc723da8..db199065 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,6 @@ common-API Piramal - Helpline: 1097 Module API - Piramal - Helpline: 1097 Module API org.springframework.boot From cd4a707e2759b5cb5f0a1e7e95b666df9c9ce1df Mon Sep 17 00:00:00 2001 From: Keval Kanpariya Date: Thu, 10 Apr 2025 17:58:33 +0530 Subject: [PATCH 11/11] unnecssary comments are removed --- src/main/java/com/iemr/common/utils/JwtUtil.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index 03d2a9b1..c0241954 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -85,19 +85,7 @@ public Claims getAllClaimsFromToken(String token) { .getPayload(); } - /** - * Retrieves the refresh token expiration time. - * - * The refresh token expiration time determines how long a refresh token remains valid. - * A longer expiration time allows users to stay logged in without frequently re-authenticating. - * - * Security & Business Considerations: - * - A longer expiration (e.g., 7 days or more) improves user experience but may pose security risks if tokens are leaked. - * - A shorter expiration (e.g., 1 hour) enhances security but may require users to log in more frequently. - * - This duration is configurable and can be overridden in the environment specific application properties file. - * - * @return The expiration time in milliseconds. - */ + public long getRefreshTokenExpiration() { return REFRESH_EXPIRATION_TIME; }