-
Notifications
You must be signed in to change notification settings - Fork 43
middleware added through FilterConfig for JWT Token Validation. #87
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 1 commit
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -218,6 +218,40 @@ | |||||||||||||||||||||
| <artifactId>spring-web</artifactId> | ||||||||||||||||||||||
| <version>6.1.12</version> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <!-- Jersey JSON Processing --> | ||||||||||||||||||||||
| <dependency> | ||||||||||||||||||||||
| <groupId>org.glassfish.jersey.media</groupId> | ||||||||||||||||||||||
| <artifactId>jersey-media-json-processing</artifactId> | ||||||||||||||||||||||
| <version>2.30.1</version> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <!-- JAXB Runtime Implementation --> | ||||||||||||||||||||||
| <dependency> | ||||||||||||||||||||||
| <groupId>org.glassfish.jaxb</groupId> | ||||||||||||||||||||||
| <artifactId>jaxb-runtime</artifactId> | ||||||||||||||||||||||
| <version>2.3.1</version> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
|
Comment on lines
+230
to
+234
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 Update JAXB Runtime version for compatibility. The project is using JAXB Runtime 2.3.1, which is quite old and may not be fully compatible with Jakarta EE in Spring Boot 3.x. Spring Boot 3 uses Jakarta EE 9+ which requires newer JAXB versions. Consider updating to a newer version that aligns with Jakarta EE: <dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
- <version>2.3.1</version>
+ <version>4.0.0</version>
</dependency>π Committable suggestion
Suggested change
|
||||||||||||||||||||||
| <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --> | ||||||||||||||||||||||
| <dependency> | ||||||||||||||||||||||
| <groupId>io.jsonwebtoken</groupId> | ||||||||||||||||||||||
| <artifactId>jjwt-api</artifactId> | ||||||||||||||||||||||
| <version>0.12.6</version> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
| <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --> | ||||||||||||||||||||||
| <dependency> | ||||||||||||||||||||||
| <groupId>io.jsonwebtoken</groupId> | ||||||||||||||||||||||
| <artifactId>jjwt-impl</artifactId> | ||||||||||||||||||||||
| <version>0.12.6</version> | ||||||||||||||||||||||
| <scope>runtime</scope> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
| <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --> | ||||||||||||||||||||||
| <dependency> | ||||||||||||||||||||||
| <groupId>io.jsonwebtoken</groupId> | ||||||||||||||||||||||
| <artifactId>jjwt-jackson</artifactId> | ||||||||||||||||||||||
| <version>0.12.6</version> | ||||||||||||||||||||||
| <scope>runtime</scope> | ||||||||||||||||||||||
| </dependency> | ||||||||||||||||||||||
| <!--END NEW DEPENDENCIES ADDED--> | ||||||||||||||||||||||
| </dependencies> | ||||||||||||||||||||||
| <profiles> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Member
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. @kevalkanp1011 are all these attributes required for JWT checks?
Author
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. not all but username, password, userid are required. i followed similar implementation available in other repositories |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,142 @@ | ||||||||||||
| package com.iemr.common.identity.domain.iemr; | ||||||||||||
|
|
||||||||||||
| import com.google.gson.annotations.Expose; | ||||||||||||
| import jakarta.persistence.*; | ||||||||||||
| import lombok.Data; | ||||||||||||
| import java.sql.Date; | ||||||||||||
| import java.sql.Timestamp; | ||||||||||||
| import java.time.LocalDate; | ||||||||||||
|
|
||||||||||||
| @Entity | ||||||||||||
| @Table(name = "m_User",schema = "db_iemr") | ||||||||||||
| @Data | ||||||||||||
| public class M_User { | ||||||||||||
|
|
||||||||||||
| @Id | ||||||||||||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="UserID") | ||||||||||||
| private Integer userID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="TitleID") | ||||||||||||
| private Integer titleID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="FirstName") | ||||||||||||
| private String firstName; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="MiddleName") | ||||||||||||
| private String middleName; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="LastName") | ||||||||||||
| private String lastName; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="GenderID") | ||||||||||||
| private Short genderID; | ||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="MaritalStatusID") | ||||||||||||
| private Integer maritalStatusID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="DesignationID") | ||||||||||||
| private Integer designationID; | ||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="AadhaarNo") | ||||||||||||
| private String aadhaarNo; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="PAN") | ||||||||||||
| private String pAN; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="DOB") | ||||||||||||
| private LocalDate dOB; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="DOJ") | ||||||||||||
| private LocalDate dOJ; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="QualificationID") | ||||||||||||
| private Integer qualificationID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="HealthProfessionalID") | ||||||||||||
| private String healthProfessionalID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="UserName") | ||||||||||||
| private String userName; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="Password") | ||||||||||||
| private String password; | ||||||||||||
|
Comment on lines
+64
to
+66
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. Secure sensitive information storage. The password field is currently exposed via the Consider:
- @Expose
@Column(name="Password")
private String password;π Committable suggestion
Suggested change
Member
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. 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.
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="IsExternal") | ||||||||||||
| private Boolean isExternal; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="AgentID") | ||||||||||||
| private String agentID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="AgentPassword") | ||||||||||||
| private String agentPassword; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="EmailID") | ||||||||||||
| private String emailID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="StatusID") | ||||||||||||
| private Integer statusID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="EmergencyContactPerson") | ||||||||||||
| private String emergencyContactPerson; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="EmergencyContactNo") | ||||||||||||
| private String emergencyContactNo; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="IsSupervisor") | ||||||||||||
| private Boolean isSupervisor; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="Deleted",insertable = false, updatable = true) | ||||||||||||
| private Boolean deleted; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="CreatedBy") | ||||||||||||
| private String createdBy; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="EmployeeID") | ||||||||||||
| private String employeeID; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="CreatedDate",insertable = false, updatable = false) | ||||||||||||
| private Timestamp createdDate; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="ModifiedBy") | ||||||||||||
| private String modifiedBy; | ||||||||||||
| @Expose | ||||||||||||
| @Column(name="LastModDate",insertable = false, updatable = false) | ||||||||||||
| private Timestamp lastModDate; | ||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="Remarks") | ||||||||||||
| private String remarks; | ||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="ContactNo") | ||||||||||||
| private String contactNo; | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="IsProviderAdmin") | ||||||||||||
| private Boolean isProviderAdmin; | ||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name="ServiceProviderID") | ||||||||||||
| private Integer serviceProviderID; | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| @Expose | ||||||||||||
| @Column(name = "failed_attempt", insertable = false) | ||||||||||||
| private Integer failedAttempt; | ||||||||||||
| public M_User() { | ||||||||||||
| // TODO Auto-generated constructor stub | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public M_User(Integer userID, String userName) { | ||||||||||||
| // TODO Auto-generated constructor stub | ||||||||||||
| this.userID=userID; | ||||||||||||
| this.userName=userName; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| } | ||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.iemr.common.identity.repo.iemr; | ||
|
|
||
| import com.iemr.common.identity.domain.iemr.M_User; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| public interface EmployeeMasterRepo extends JpaRepository<M_User,Integer> { | ||
| M_User findByUserID(Integer userID); | ||
|
|
||
| M_User getUserByUserID(Integer parseLong); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.iemr.common.identity.utils; | ||
|
|
||
| import jakarta.servlet.http.Cookie; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import org.springframework.stereotype.Service; | ||
| import java.util.Arrays; | ||
| import java.util.Optional; | ||
|
|
||
| @Service | ||
| public class CookieUtil { | ||
|
|
||
| public Optional<String> getCookieValue(HttpServletRequest request, String cookieName) { | ||
| Cookie[] cookies = request.getCookies(); | ||
| if (cookies != null) { | ||
| for (Cookie cookie : cookies) { | ||
| if (cookieName.equals(cookie.getName())) { | ||
| return Optional.of(cookie.getValue()); | ||
| } | ||
| } | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response, HttpServletRequest request) { | ||
| // Create a new cookie with the JWT token | ||
| Cookie cookie = new Cookie("Jwttoken", Jwttoken); | ||
| cookie.setHttpOnly(true); // Prevent JavaScript access for security | ||
| cookie.setMaxAge(60 * 60 * 24); // 1 day expiration time | ||
| cookie.setPath("/"); // Make the cookie available for the entire application | ||
| if ("https".equalsIgnoreCase(request.getScheme())) { | ||
| cookie.setSecure(true); // Secure flag only on HTTPS | ||
| } | ||
| response.addCookie(cookie); // Add the cookie to the response | ||
| } | ||
|
Comment on lines
+25
to
+35
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 CSRF mitigation measures to cookies. π§° Toolsπͺ ast-grep (0.31.1)[warning] 33-33: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'. (cookie-missing-secure-flag-java) [warning] 33-33: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The (cookie-missing-samesite-java) |
||
|
|
||
| public String getJwtTokenFromCookie(HttpServletRequest request) { | ||
| return Arrays.stream(request.getCookies()).filter(cookie -> "Jwttoken".equals(cookie.getName())) | ||
| .map(Cookie::getValue).findFirst().orElse(null); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.iemr.common.identity.utils; | ||
|
|
||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class FilterConfig { | ||
|
|
||
| @Bean | ||
| public FilterRegistrationBean<JwtUserIdValidationFilter> jwtUserIdValidationFilter( | ||
| JwtAuthenticationUtil jwtAuthenticationUtil) { | ||
| FilterRegistrationBean<JwtUserIdValidationFilter> registrationBean = new FilterRegistrationBean<>(); | ||
| registrationBean.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil)); | ||
| registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints | ||
| return registrationBean; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package com.iemr.common.identity.utils; | ||
|
|
||
| import com.iemr.common.identity.domain.iemr.M_User; | ||
| import com.iemr.common.identity.repo.iemr.EmployeeMasterRepo; | ||
| import com.iemr.common.identity.utils.exception.IEMRException; | ||
| import io.jsonwebtoken.Claims; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| @Component | ||
| public class JwtAuthenticationUtil { | ||
|
|
||
| @Autowired | ||
| private CookieUtil cookieUtil; | ||
| @Autowired | ||
| private JwtUtil jwtUtil; | ||
| @Autowired | ||
| private RedisTemplate<String, Object> redisTemplate; | ||
| @Autowired | ||
| private EmployeeMasterRepo userLoginRepo; | ||
| private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); | ||
|
|
||
| public JwtAuthenticationUtil(CookieUtil cookieUtil, JwtUtil jwtUtil) { | ||
| this.cookieUtil = cookieUtil; | ||
| this.jwtUtil = jwtUtil; | ||
| } | ||
|
|
||
| public ResponseEntity<String> validateJwtToken(HttpServletRequest request) { | ||
| Optional<String> jwtTokenOpt = cookieUtil.getCookieValue(request, "Jwttoken"); | ||
|
|
||
| if (jwtTokenOpt.isEmpty()) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
| .body("Error 401: Unauthorized - JWT Token is not set!"); | ||
| } | ||
|
|
||
| String jwtToken = jwtTokenOpt.get(); | ||
|
|
||
| // Validate the token | ||
| Claims claims = jwtUtil.validateToken(jwtToken); | ||
| if (claims == null) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Error 401: Unauthorized - Invalid JWT Token!"); | ||
| } | ||
|
|
||
| // Extract username from token | ||
| String usernameFromToken = claims.getSubject(); | ||
| if (usernameFromToken == null || usernameFromToken.isEmpty()) { | ||
| return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
| .body("Error 401: Unauthorized - Username is missing!"); | ||
| } | ||
|
|
||
| // Return the username if valid | ||
| return ResponseEntity.ok(usernameFromToken); | ||
| } | ||
|
|
||
| public boolean validateUserIdAndJwtToken(String jwtToken) throws IEMRException { | ||
| try { | ||
| // Validate JWT token and extract claims | ||
| Claims claims = jwtUtil.validateToken(jwtToken); | ||
|
|
||
| if (claims == null) { | ||
| throw new IEMRException("Invalid JWT token."); | ||
| } | ||
|
|
||
| String userId = claims.get("userId", String.class); | ||
|
|
||
| // Check if user data is present in Redis | ||
| M_User user = getUserFromCache(userId); | ||
| if (user == null) { | ||
| // If not in Redis, fetch from DB and cache the result | ||
| user = fetchUserFromDB(userId); | ||
| } | ||
| if (user == null) { | ||
| throw new IEMRException("Invalid User ID."); | ||
| } | ||
|
|
||
| return true; // Valid userId and JWT token | ||
| } catch (Exception e) { | ||
| logger.error("Validation failed: " + e.getMessage(), e); | ||
| throw new IEMRException("Validation error: " + e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| private M_User getUserFromCache(String userId) { | ||
| String redisKey = "user_" + userId; // The Redis key format | ||
| M_User user = (M_User) redisTemplate.opsForValue().get(redisKey); | ||
|
|
||
| if (user == null) { | ||
| logger.warn("User not found in Redis. Will try to fetch from DB."); | ||
| } else { | ||
| logger.info("User fetched successfully from Redis."); | ||
| } | ||
|
|
||
| return user; // Returns null if not found | ||
| } | ||
|
|
||
| private M_User fetchUserFromDB(String userId) { | ||
| // This method will only be called if the user is not found in Redis. | ||
| String redisKey = "user_" + userId; // Redis key format | ||
|
|
||
| // Fetch user from DB | ||
| M_User user = userLoginRepo.getUserByUserID(Integer.parseInt(userId)); | ||
|
|
||
| if (user != null) { | ||
| // Cache the user in Redis for future requests (cache for 30 minutes) | ||
| redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); | ||
|
|
||
| // Log that the user has been stored in Redis | ||
| logger.info("User stored in Redis with key: " + redisKey); | ||
| } else { | ||
| logger.warn("User not found for userId: " + userId); | ||
| } | ||
|
|
||
| return user; | ||
| } | ||
| } |
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.
π οΈ Refactor suggestion
Update Jersey JSON Processing version for compatibility.
The version of jersey-media-json-processing (2.30.1) is outdated and may have compatibility issues with Spring Boot 3.x and Jakarta EE.
Consider updating to a version that supports Jakarta EE:
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-processing</artifactId> - <version>2.30.1</version> + <version>3.1.3</version> </dependency>π Committable suggestion