diff --git a/pom.xml b/pom.xml index c5e15820..a9499b72 100644 --- a/pom.xml +++ b/pom.xml @@ -220,6 +220,26 @@ spring-web 6.1.12 + + + io.jsonwebtoken + jjwt-api + 0.12.6 + + + + io.jsonwebtoken + jjwt-impl + 0.12.6 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.6 + runtime + diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index a2b79037..d046eeec 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -11,13 +11,15 @@ logging.file.name=@env.IDENTITY_API_1097_LOGGING_FILE_NAME@ springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ -jwt.secret=@JWT_SECRET_KEY@ +jwt.secret=@env.JWT_SECRET_KEY@ # TM Config -tm-url=@TM_API@ +tm-url=@env.TM_API@ # FHIR Config -fhir-url=@FHIR_API@ +fhir-url=@env.FHIR_API@ # Redis Config -spring.redis.host=@env.REDIS_HOST@ \ No newline at end of file +spring.redis.host=@env.REDIS_HOST@ + +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/1097_docker.properties b/src/main/environment/1097_docker.properties index 5510dfce..471665aa 100644 --- a/src/main/environment/1097_docker.properties +++ b/src/main/environment/1097_docker.properties @@ -19,4 +19,6 @@ tm-url=${TM_API} fhir-url=${FHIR_API} # Redis Config -spring.redis.host=${REDIS_HOST} \ No newline at end of file +spring.redis.host=${REDIS_HOST} + +cors.allowed-origins=${CORS_ALLOWED_ORIGINS} diff --git a/src/main/environment/1097_example.properties b/src/main/environment/1097_example.properties index 9eeee5c4..d1f66965 100644 --- a/src/main/environment/1097_example.properties +++ b/src/main/environment/1097_example.properties @@ -17,4 +17,5 @@ tm-url=http://localhost:8089/ fhir-url=http://localhost:8093/ # Redis Config -spring.redis.host=localhost \ No newline at end of file +spring.redis.host=localhost +cors.allowed-origins=http://localhost:* diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index debf33c4..75ad8759 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -11,13 +11,15 @@ logging.file.name=@env.IDENTITY_API_LOGGING_FILE_NAME@ springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ -jwt.secret=@JWT_SECRET_KEY@ +jwt.secret=@env.JWT_SECRET_KEY@ # TM Config -tm-url=@TM_API@ +tm-url=@env.TM_API@ # FHIR Config -fhir-url=@FHIR_API@ +fhir-url=@env.FHIR_API@ # Redis Config -spring.redis.host=@env.REDIS_HOST@ \ No newline at end of file +spring.redis.host=@env.REDIS_HOST@ + +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 4409906e..90942c28 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -20,4 +20,6 @@ tm-url=${TM_API} fhir-url=${FHIR_API} # Redis Config -spring.redis.host=${REDIS_HOST} \ No newline at end of file +spring.redis.host=${REDIS_HOST} + +cors.allowed-origins=${CORS_ALLOWED_ORIGINS} diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index ca58c85c..ad15db00 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -16,4 +16,5 @@ tm-url=http://localhost:8089/ fhir-url=http://localhost:8093/ # Redis Config -spring.redis.host=localhost \ No newline at end of file +spring.redis.host=localhost +cors.allowed-origins=http://localhost:* diff --git a/src/main/java/com/iemr/common/identity/config/CorsConfig.java b/src/main/java/com/iemr/common/identity/config/CorsConfig.java new file mode 100644 index 00000000..d52f7e35 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/config/CorsConfig.java @@ -0,0 +1,23 @@ +package com.iemr.common.identity.config; + +import java.util.Arrays; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOriginPatterns( + allowedOrigins != null && !allowedOrigins.trim().isEmpty() ? Arrays.stream(allowedOrigins.split(",")) + .map(String::trim).filter(s -> !s.isEmpty()).toArray(String[]::new) : new String[0]) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*") + .exposedHeaders("Authorization", "Jwttoken").allowCredentials(true).maxAge(3600); + } +} diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index 03484813..4ac603ae 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -33,7 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -83,7 +82,6 @@ public class IdentityController { @Autowired IdentityMapper mapper; - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries by advance search") @PostMapping(path = "/advanceSearch", headers = "Authorization") public String getBeneficiaries( @@ -112,7 +110,6 @@ public String getBeneficiaries( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on beneficiary registration id") @PostMapping(path = "/getByBenRegId", headers = "Authorization") public String getBeneficiariesByBeneficiaryRegId( @@ -137,7 +134,6 @@ public String getBeneficiariesByBeneficiaryRegId( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search identity based on beneficiary registration id") @PostMapping(path = "/getByBenId", headers = "Authorization") public String getBeneficiariesByBeneficiaryId( @@ -172,7 +168,6 @@ public String getBeneficiariesByBeneficiaryId( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on phone number") @PostMapping(path = "/getByPhoneNum", headers = "Authorization") public String getBeneficiariesByPhoneNum( @@ -203,7 +198,6 @@ public String getBeneficiariesByPhoneNum( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on health ID / ABHA Address") @PostMapping(path = "/getByAbhaAddress", headers = "Authorization") public String searhBeneficiaryByABHAAddress( @@ -235,7 +229,6 @@ public String searhBeneficiaryByABHAAddress( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on health ID number / ABHA ID number") @PostMapping(path = "/getByAbhaIdNo", headers = "Authorization") public String searhBeneficiaryByABHAIdNo( @@ -267,7 +260,6 @@ public String searhBeneficiaryByABHAIdNo( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on family id") @PostMapping(path = "/searchByFamilyId", headers = "Authorization") public String searhBeneficiaryByFamilyId( @@ -297,7 +289,6 @@ public String searhBeneficiaryByFamilyId( } // search beneficiary by lastModDate and districtID - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary ="Search beneficiary by villageId and last modified date-time") @PostMapping(path = "/searchByVillageIdAndLastModifiedDate") public String searchBeneficiaryByVillageIdAndLastModDate( @@ -322,7 +313,6 @@ public String searchBeneficiaryByVillageIdAndLastModDate( return response; } // search beneficiary by lastModDate and districtID - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary ="Get count of beneficiary by villageId and last modified date-time") @PostMapping(path = "/countBenByVillageIdAndLastModifiedDate") public String countBeneficiaryByVillageIdAndLastModDate( @@ -342,7 +332,6 @@ public String countBeneficiaryByVillageIdAndLastModDate( } return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on government identity number") @PostMapping(path = "/searhByGovIdentity", headers = "Authorization") public String searhBeneficiaryByGovIdentity( @@ -376,7 +365,6 @@ public String searhBeneficiaryByGovIdentity( * @param identityEditData * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Edit identity by agent") @PostMapping(path = "/edit", headers = "Authorization") public String editIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"String\",\r\n" @@ -508,7 +496,6 @@ public String editIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"Stri * @param identityData * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Create identity by agent") @PostMapping(path = "/create", headers = "Authorization") public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"String\",\r\n" @@ -615,7 +602,6 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Reserve identity by agent") @PostMapping(path = "/reserve", headers = "Authorization") public String reserveIdentity(@RequestBody String reserveIdentity) { @@ -635,7 +621,6 @@ public String reserveIdentity(@RequestBody String reserveIdentity) { return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Unreserve identity by agent") @PostMapping(path = "/unreserve", headers = "Authorization") public String unreserveIdentity(@RequestBody String unreserve) { @@ -661,7 +646,6 @@ public String unreserveIdentity(@RequestBody String unreserve) { * @param benRegIds * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries partial details by beneficiary registration id list") @PostMapping(path = "/getByPartialBenRegIdList", headers = "Authorization") public String getPartialBeneficiariesByBenRegIds( @@ -693,7 +677,6 @@ public String getPartialBeneficiariesByBenRegIds( * @param benRegIds * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries by beneficiary registration id") @PostMapping(path = "/getByBenRegIdList", headers = "Authorization") public String getBeneficiariesByBenRegIds( @@ -792,7 +775,6 @@ public String getJsonAsString(Object obj) { return sb.toString(); } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get finite beneficiaries") @PostMapping(path = "/finiteSearch", headers = "Authorization") public String getFiniteBeneficiaries(@RequestBody String searchFilter) { @@ -816,7 +798,6 @@ public String getFiniteBeneficiaries(@RequestBody String searchFilter) { } // New API for getting beneficiary image only. - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiary image by beneficiary registration id") @PostMapping(path = "/benImageByBenRegID", headers = "Authorization") public String getBeneficiaryImageByBenRegID(@RequestBody String identityData) { @@ -830,7 +811,6 @@ public String getBeneficiaryImageByBenRegID(@RequestBody String identityData) { return benImage; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Edit education or community by agent") @PostMapping(path = "/editEducationOrCommunity", headers = "Authorization") public String editIdentityEducationOrCommunity(@Param(value = "{\r\n" @@ -957,7 +937,6 @@ public String editIdentityEducationOrCommunity(@Param(value = "{\r\n" } } - @CrossOrigin() @Operation(summary = "Check available beneficary id in local server") @GetMapping(path = "/checkAvailablBenIDLocalServer", headers = "Authorization") public String checkAvailablBenIDLocalServer() { @@ -972,7 +951,6 @@ public String checkAvailablBenIDLocalServer() { return response.toString(); } - // @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Save server generated beneficiary ID & beneficiary registration ID to local server") @PostMapping(path = "/saveGeneratedBenIDToLocalServer", headers = "Authorization", consumes = "application/json", produces = "application/json") public String saveGeneratedBenIDToLocalServer( diff --git a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java index b1f117c7..19323611 100644 --- a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java +++ b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -35,7 +34,6 @@ import io.swagger.v3.oas.annotations.Operation; -@CrossOrigin @RestController @RequestMapping({ "/family" }) public class FamilyTaggingController { @@ -43,7 +41,6 @@ public class FamilyTaggingController { @Autowired private FamilyTagService familyTagService; - @CrossOrigin() @Operation(summary = "Create and modify family tagging") @PostMapping(value = { "/addTag" }, consumes = "application/json", produces = "application/json") public String saveFamilyTagging(@RequestBody String comingReq) { @@ -59,7 +56,6 @@ public String saveFamilyTagging(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Create family") @PostMapping(value = { "/createFamily" }, consumes = "application/json", produces = "application/json") public String createFamily(@RequestBody String comingReq) { @@ -75,7 +71,6 @@ public String createFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Search family") @PostMapping(value = { "/searchFamily" }, consumes = "application/json", produces = "application/json") public String searchFamily(@RequestBody String comingReq) { @@ -91,7 +86,6 @@ public String searchFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Get family members details") @PostMapping(value = { "/getFamilyDetails" }, consumes = "application/json", produces = "application/json") public String getFamilyDatails(@RequestBody String comingReq) { @@ -107,7 +101,6 @@ public String getFamilyDatails(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Untag beneficiary from a family") @PostMapping(value = { "/untag" }, consumes = "application/json", produces = "application/json") public String untagFamily(@RequestBody String comingReq) { @@ -123,7 +116,6 @@ public String untagFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Edit beneficiary family details") @PostMapping(value = { "/editFamilyTagging" }, consumes = "application/json", produces = "application/json") public String editFamilyDetails(@RequestBody String comingReq) { diff --git a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java index fdf973cd..4435c3db 100644 --- a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java +++ b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -41,7 +40,6 @@ public class VersionController { private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); - @CrossOrigin() @Operation(summary = "Get version information") @GetMapping(value = "/version",consumes = "application/json", produces = "application/json") public String versionInformation() { diff --git a/src/main/java/com/iemr/common/identity/domain/User.java b/src/main/java/com/iemr/common/identity/domain/User.java new file mode 100644 index 00000000..cf0f5053 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/domain/User.java @@ -0,0 +1,147 @@ +package com.iemr.common.identity.domain; + +import java.sql.Timestamp; +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.gson.annotations.Expose; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +@Entity +@Table(name = "m_User") +@JsonIgnoreProperties(ignoreUnknown = true) +@Data +public class 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; + + @Column(name="AadhaarNo") + private String aadhaarNo; + @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; + @JsonIgnore + @Column(name="Password") + private String password; + @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 User() { + // TODO Auto-generated constructor stub + } + + public User(Integer userID, String userName) { + // TODO Auto-generated constructor stub + this.userID=userID; + this.userName=userName; + } + +} diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 531913e8..a0b8761f 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -1747,7 +1747,7 @@ public int importBenIdToLocalServer(List benIdImportDTOList) { jdbcTemplate = getJdbcTemplate(); List dataList = new ArrayList<>(); Object[] objArr; - String query = " INSERT INTO db_identity.m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " + String query = " INSERT INTO m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " + " Provisioned, CreatedDate, CreatedBy, Reserved) VALUES (?,?,?,?,?,?) "; logger.info("query : " + query); for (MBeneficiaryregidmapping obj : mBeneficiaryregidmappingList) { diff --git a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java new file mode 100644 index 00000000..84f54526 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java @@ -0,0 +1,28 @@ +package com.iemr.common.identity.utils; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; + +@Service +public class CookieUtil { + + public Optional 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 String getJwtTokenFromCookie(HttpServletRequest request) { + return getCookieValue(request, "Jwttoken").orElse(null); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/FilterConfig.java b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java new file mode 100644 index 00000000..e13b8b4e --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java @@ -0,0 +1,26 @@ +package com.iemr.common.identity.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; + +@Configuration +public class FilterConfig { + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Bean + public FilterRegistrationBean jwtUserIdValidationFilter( + JwtAuthenticationUtil jwtAuthenticationUtil, CookieUtil cookieUtil) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + + JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins, cookieUtil); + registrationBean.setFilter(filter); + registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); + registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints + return registrationBean; + } + +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java new file mode 100644 index 00000000..49e48265 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java @@ -0,0 +1,125 @@ +package com.iemr.common.identity.utils; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +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.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import com.iemr.common.identity.domain.User; +import com.iemr.common.identity.exception.IEMRException; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; + +@Component +public class JwtAuthenticationUtil { + + private final CookieUtil cookieUtil; + private final JwtUtil jwtUtil; + private final RedisTemplate redisTemplate; + private final JdbcTemplate jdbcTemplate; + + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + // ✅ Constructor injection for all dependencies + @Autowired + public JwtAuthenticationUtil( + CookieUtil cookieUtil, + JwtUtil jwtUtil, + RedisTemplate redisTemplate, + JdbcTemplate jdbcTemplate) { + + this.cookieUtil = cookieUtil; + this.jwtUtil = jwtUtil; + this.redisTemplate = redisTemplate; + this.jdbcTemplate = jdbcTemplate; + } + + public ResponseEntity validateJwtToken(HttpServletRequest request) { + Optional 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 { + Claims claims = jwtUtil.validateToken(jwtToken); + if (claims == null) { + throw new IEMRException("Invalid JWT token."); + } + String userId = claims.get("userId", String.class); + User user = getUserFromCache(userId); + if (user == null) { + 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 User getUserFromCache(String userId) { + String redisKey = "user_" + userId; // The Redis key format + User user = (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 User fetchUserFromDB(String userId) { + String redisKey = "user_" + userId; // Redis key format + List users = jdbcTemplate.query( + "SELECT * FROM m_user WHERE UserID = ? AND Deleted = false", + new BeanPropertyRowMapper<>(User.class), userId); + + if (users.isEmpty()) { + logger.warn("User not found for userId: " + userId); + return null; + } + + User user = users.get(0); + redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); + logger.info("User stored in Redis with key: " + redisKey); + return user; + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java new file mode 100644 index 00000000..a030ab31 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -0,0 +1,154 @@ +package com.iemr.common.identity.utils; + +import java.io.IOException; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iemr.common.identity.utils.http.AuthorizationHeaderRequestWrapper; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class JwtUserIdValidationFilter implements Filter { + private final JwtAuthenticationUtil jwtAuthenticationUtil; + private final CookieUtil cookieUtil; + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + private final String allowedOrigins; + + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, String allowedOrigins, CookieUtil cookieUtil) { + this.jwtAuthenticationUtil = jwtAuthenticationUtil; + this.allowedOrigins = allowedOrigins; + this.cookieUtil = cookieUtil; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + handleCorsHeaders(request, response); + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + logger.info("OPTIONS request - skipping JWT validation"); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + String path = request.getRequestURI(); + logger.info("JwtUserIdValidationFilter invoked for path: " + path); + + // Log cookies for debugging + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("userId".equalsIgnoreCase(cookie.getName())) { + logger.warn("userId found in cookies! Clearing it..."); + clearUserIdCookie(response); // Explicitly remove userId cookie + } + } + } else { + logger.info("No cookies found in the request"); + } + + // Log headers for debugging + logger.info("JWT token from header: "); + + try { + String jwtFromCookie = getJwtTokenFromCookies(request); + String jwtFromHeader = request.getHeader("JwtToken"); + String authHeader = request.getHeader("Authorization"); + String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; + if (jwtToken != null) { + logger.info("Validating JWT token from cookie"); + if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken)) { + AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper( + request, ""); + filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse); + return; + } + } else { + String userAgent = request.getHeader("User-Agent"); + logger.info("User-Agent: " + userAgent); + if (userAgent != null && isMobileClient(userAgent) && authHeader != null) { + try { + logger.info("Common-API incoming userAget : " + userAgent); + UserAgentContext.setUserAgent(userAgent); + filterChain.doFilter(servletRequest, servletResponse); + } finally { + UserAgentContext.clear(); + } + return; + } + } + + logger.warn("No valid authentication token found"); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token"); + } catch (Exception e) { + logger.error("Authorization error: ", e); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization error: " + e.getMessage()); + } + } + + private void handleCorsHeaders(HttpServletRequest request, HttpServletResponse response) { + String origin = request.getHeader("Origin"); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, JwtToken, Jwttoken"); + response.setHeader("Vary", "Origin"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else { + logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + } + + } + + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + logger.warn("No allowed origins configured or origin is null"); + return false; + } + + return Arrays.stream(allowedOrigins.split(",")).map(String::trim).anyMatch(pattern -> { + String regex = pattern.replace(".", "\\.").replace("*", ".*").replace("http://localhost:.*", + "http://localhost:\\d+"); // special case for wildcard port + + boolean matched = origin.matches(regex); + return matched; + }); + } + + private boolean isMobileClient(String userAgent) { + if (userAgent == null) + return false; + userAgent = userAgent.toLowerCase(); + return userAgent.contains("okhttp"); // iOS (custom clients) + } + + private String getJwtTokenFromCookies(HttpServletRequest request) { + return cookieUtil.getJwtTokenFromCookie(request); + } + + private void clearUserIdCookie(HttpServletResponse response) { + Cookie cookie = new Cookie("userId", null); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setMaxAge(0); // Invalidate the cookie + cookie.setAttribute("SameSite", "Strict"); + response.addCookie(cookie); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java new file mode 100644 index 00000000..5d3dc830 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java @@ -0,0 +1,168 @@ +package com.iemr.common.identity.utils; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.UUID; +import java.util.function.Function; + +import javax.crypto.SecretKey; + +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; +@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; + + @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(StandardCharsets.UTF_8)); + } + + /** + * 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) { + if (username == null || username.trim().isEmpty()) { + throw new IllegalArgumentException("Username cannot be null or empty"); + } + if (userId == null || userId.trim().isEmpty()) { + throw new IllegalArgumentException("User ID cannot be null or empty"); + } + 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) { + try { + Claims claims = Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload(); + String jti = claims.getId(); + + // Check if token is denylisted (only if jti exists) + if (jti != null && tokenDenylist.isTokenDenylisted(jti)) { + return null; + } + return claims; + } 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.) + } + } + + /** + * Extract claims from the token + * @param token the JWT token + * @return all claims from the token + */ + public Claims getAllClaimsFromToken(String token) { + Claims claims = validateToken(token); + if (claims == null) { + throw new IllegalArgumentException("Invalid or denylisted token"); + } + return claims; + + } + + /** + * Extract a specific claim from the token using a function + * @param token the JWT token + * @param claimsResolver the function to extract the claim + * @param the type of the claim + * @return the extracted claim + */ + public T getClaimFromToken(String token, Function 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; + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java new file mode 100644 index 00000000..1248f478 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -0,0 +1,72 @@ +package com.iemr.common.identity.utils; + +import java.util.concurrent.TimeUnit; + +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.stereotype.Component; + +import com.iemr.common.identity.utils.exception.TokenDenylistException; +@Component +public class TokenDenylist { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private static final String PREFIX = "denied_"; + + @Autowired + private RedisTemplate redisTemplate; + + private String getKey(String jti) { + return PREFIX + jti; + } + + public void addTokenToDenylist(String jti, Long expirationTime) { + if (jti == null || jti.trim().isEmpty()) { + logger.warn("Attempted to add null or empty jti to denylist"); + return; + } + if (expirationTime == null || expirationTime <= 0) { + logger.error("Invalid expiration time for jti: {}", jti); + throw new IllegalArgumentException("Expiration time must be positive"); + } + try { + String key = getKey(jti); // Use helper method to get the key + redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS); + logger.debug("Added jti to denylist: {}", jti); + } catch (Exception e) { + logger.error("Failed to denylist token with jti: {}", jti, e); + throw new TokenDenylistException("Failed to denylist token", e); + } + } + + public boolean isTokenDenylisted(String jti) { + if (jti == null || jti.trim().isEmpty()) { + return false; + } + try { + String key = getKey(jti); // Use helper method to get the key + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } catch (Exception e) { + logger.error("Failed to check denylist status for jti: " + jti, e); + throw new TokenDenylistException("Unable to verify token denylist status", e); + } + } + + // Remove a token's jti from the denylist (Redis) + public void removeTokenFromDenylist(String jti) { + if (jti != null && !jti.trim().isEmpty()) { + try { + String key = getKey(jti); + redisTemplate.delete(key); + logger.debug("Removed jti from denylist: {}", jti); + } catch (Exception e) { + logger.error("Failed to remove token from denylist with jti: {}", jti, e); + throw new TokenDenylistException("Failed to remove token from denylist", e); + } + } else { + logger.warn("Attempted to remove null or empty jti from denylist"); + } + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java new file mode 100644 index 00000000..59cc804b --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java @@ -0,0 +1,20 @@ +package com.iemr.common.identity.utils; + +public class UserAgentContext { + private static final ThreadLocal userAgentHolder = new ThreadLocal<>(); + + public static void setUserAgent(String userAgent) { + if (userAgent != null && userAgent.trim().isEmpty()) { + userAgent = null; // Treat empty strings as null + } + userAgentHolder.set(userAgent); + } + + public static String getUserAgent() { + return userAgentHolder.get(); + } + + public static void clear() { + userAgentHolder.remove(); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java b/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java new file mode 100644 index 00000000..d2d16985 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java @@ -0,0 +1,17 @@ +package com.iemr.common.identity.utils.exception; + +public class TokenDenylistException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public TokenDenylistException(String message) { + super(message); + } + + public TokenDenylistException(String message, Throwable cause) { + super(message, cause); + } + + public TokenDenylistException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java new file mode 100644 index 00000000..bbbc3de7 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java @@ -0,0 +1,44 @@ +package com.iemr.common.identity.utils.http; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +public class AuthorizationHeaderRequestWrapper extends HttpServletRequestWrapper { + private final String Authorization; + + public AuthorizationHeaderRequestWrapper(HttpServletRequest request, String authHeaderValue) { + super(request); + this.Authorization = authHeaderValue != null ? authHeaderValue : ""; + } + + @Override + public String getHeader(String name) { + if ("Authorization".equalsIgnoreCase(name)) { + return Authorization; + } + return super.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + if ("Authorization".equalsIgnoreCase(name)) { + return Authorization != null ? Collections.enumeration(Collections.singletonList(Authorization)) + : Collections.emptyEnumeration(); + } + return super.getHeaders(name); + } + + @Override + public Enumeration getHeaderNames() { + List names = Collections.list(super.getHeaderNames()); + boolean hasAuth = names.stream().anyMatch(name -> "Authorization".equalsIgnoreCase(name)); + if (!hasAuth) { + names.add("Authorization"); + } + return Collections.enumeration(names); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java index 865df356..7156102c 100644 --- a/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java @@ -102,7 +102,7 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, else authorization = postAuth; logger.debug("RequestURI::" + request.getRequestURI() + " || Authorization ::" + authorization); - if (authorization != null) { + if (authorization != null && !authorization.isEmpty()) { sessionObject.updateSessionObject(authorization, sessionObject.getSessionObject(authorization)); } } catch (Exception e) { diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java new file mode 100644 index 00000000..49969b89 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java @@ -0,0 +1,39 @@ +package com.iemr.common.identity.utils.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.session.data.redis.config.ConfigureRedisAction; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.iemr.common.identity.domain.User; + +@Configuration +public class RedisConfig { + + @Bean + public ConfigureRedisAction configureRedisAction() { + return ConfigureRedisAction.NO_OP; + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(User.class); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + serializer.setObjectMapper(mapper); + template.setValueSerializer(serializer); + + return template; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0828d00..1780d8b6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -51,15 +51,5 @@ getHealthID=healthID/getBenhealthID spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true #springfox.documentation.swagger-ui.enabled=true - - - - - - - - - - - - +jwt.access.expiration=86400000 +jwt.refresh.expiration=604800000