From 861643d486b14f57e56daf2400827c484788fa63 Mon Sep 17 00:00:00 2001 From: ravishanigarapu <133210792+ravishanigarapu@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:09:05 +0530 Subject: [PATCH] AMM-1239 : Role based broken access control (#96) * role added * Introductory Call Allocation Changes * fix(bug) :Isfurthercallrequired update fix * Unallocate Change * AMM-1239 * Code rabbit comments addressed * Code rabbit comments * review comments updated * @PreAuthorize("hasRole('SUPERVISOR')") Added for EcdQuestionareController * Role based Authentication * Security Hotspot fix * Update SecurityConfig.java * Coderabbit comments updated --- pom.xml | 4 + .../dataupload/DataTemplateController.java | 2 + .../dataupload/DataUploadController.java | 2 + .../CallStatisticsController.java | 3 +- .../OutBoundWorklistController.java | 2 + .../controller/quality/ChartsController.java | 2 + .../quality/GradeConfigurationController.java | 2 + .../quality/QualityAuditController.java | 3 +- ...yAuditQuestionConfigurationController.java | 2 + ...tyAuditSectionConfigurationController.java | 2 + ...ampleSelectionConfigurationController.java | 2 + .../questionare/EcdQuestionareController.java | 2 + .../iemr/ecd/repository/masters/RoleRepo.java | 5 +- .../service/masters/MasterServiceImpl.java | 15 +++ .../CustomAccessDeniedHandler.java | 28 ++++++ .../CustomAuthenticationEntryPoint.java | 23 +++++ .../com/iemr/ecd/utils/mapper/JwtUtil.java | 2 +- .../mapper/RoleAuthenticationFilter.java | 98 +++++++++++++++++++ .../iemr/ecd/utils/mapper/SecurityConfig.java | 52 ++++++++++ .../iemr/ecd/utils/redis/RedisStorage.java | 26 +++++ 20 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java create mode 100644 src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java create mode 100644 src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java create mode 100644 src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java diff --git a/pom.xml b/pom.xml index b97d35f..b51a43e 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,10 @@ 0.12.6 runtime + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java b/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java index 3503d77..6be8401 100644 --- a/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java +++ b/src/main/java/com/iemr/ecd/controller/dataupload/DataTemplateController.java @@ -30,6 +30,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/dataTemplate", headers = "Authorization") +@PreAuthorize("hasRole('SUPERVISOR')") public class DataTemplateController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java b/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java index c345cfd..904662d 100644 --- a/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java +++ b/src/main/java/com/iemr/ecd/controller/dataupload/DataUploadController.java @@ -26,6 +26,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -41,6 +42,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @RestController +@PreAuthorize("hasRole('SUPERVISOR')") public class DataUploadController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java b/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java index 05c71eb..5e7fac1 100644 --- a/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java +++ b/src/main/java/com/iemr/ecd/controller/outboundworklist/CallStatisticsController.java @@ -26,6 +26,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -41,7 +42,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @RestController -@RequestMapping(value = "/agent", headers = "Authorization") +@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class CallStatisticsController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java b/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java index dba679b..2ee24c6 100644 --- a/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java +++ b/src/main/java/com/iemr/ecd/controller/outboundworklist/OutBoundWorklistController.java @@ -29,6 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/outbound-worklist", headers = "Authorization") +@PreAuthorize("hasRole('ANM') || hasRole('MO') || hasRole('ASSOCIATE')") public class OutBoundWorklistController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java b/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java index 169e5c5..ea2d7ac 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/ChartsController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -44,6 +45,7 @@ @RestController @RequestMapping(value = "/charts", headers = "Authorization") +@PreAuthorize("hasRole('SUPERVISOR') || hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class ChartsController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java index da8a3ec..80fb34f 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/GradeConfigurationController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +48,7 @@ @RestController @RequestMapping(value = "/gradeConfiguration", headers = "Authorization") +@PreAuthorize("hasRole('QUALITY_SUPERVISOR')") public class GradeConfigurationController { @Autowired diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java index ff7178c..9d7ec98 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditController.java @@ -27,7 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; - +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -55,6 +55,7 @@ @RestController @RequestMapping(value = "/qualityAudit", headers = "Authorization") +@PreAuthorize("hasRole('QUALITY_AUDITOR')") public class QualityAuditController { @Autowired private QualityAuditImpl qualityAuditImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java index a0bee1d..fdbe52d 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditQuestionConfigurationController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -48,6 +49,7 @@ @RestController @RequestMapping(value = "/questionnaireConfiguration", headers = "Authorization") +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class QualityAuditQuestionConfigurationController { @Autowired private QualityAuditQuestionConfigurationImpl qualityAuditQuestionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java index 29e3060..a5f88d4 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/QualityAuditSectionConfigurationController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +48,7 @@ @RestController @RequestMapping(value = "/sectionConfiguration", headers = "Authorization") +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class QualityAuditSectionConfigurationController { @Autowired private QualityAuditSectionConfigurationImpl qualityAuditSectionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java b/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java index 11734b1..020faea 100644 --- a/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java +++ b/src/main/java/com/iemr/ecd/controller/quality/SampleSelectionConfigurationController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +48,7 @@ @RestController @RequestMapping(value = "/sampleSelectionConfiguration", headers = "Authorization") +@PreAuthorize("hasRole('QUALITY_SUPERVISOR') || hasRole('QUALITY_AUDITOR')") public class SampleSelectionConfigurationController { @Autowired private SampleSelectionConfigurationImpl sampleSelectionConfigurationImpl; diff --git a/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java b/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java index 1a7d675..e1307d6 100644 --- a/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java +++ b/src/main/java/com/iemr/ecd/controller/questionare/EcdQuestionareController.java @@ -28,6 +28,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -56,6 +57,7 @@ @RestController @RequestMapping(value = "/Questionnaire", headers = "Authorization") +@PreAuthorize("hasRole('SUPERVISOR')") public class EcdQuestionareController { @Autowired diff --git a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java index d6e3d64..9351d4a 100644 --- a/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java +++ b/src/main/java/com/iemr/ecd/repository/masters/RoleRepo.java @@ -23,7 +23,9 @@ import java.util.List; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.iemr.ecd.dao.masters.Role; @@ -32,5 +34,6 @@ public interface RoleRepo extends CrudRepository { List findByPsmIdAndDeleted(Integer psmId, Boolean deleted); - + @Query(nativeQuery = true,value = "select rolename from m_role where roleid in (select roleid from m_userservicerolemapping where userid=:userID)") + List getRoleNamebyUserId(@Param("userID") Long userID); } diff --git a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java index 54042fe..2206dd5 100644 --- a/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java +++ b/src/main/java/com/iemr/ecd/service/masters/MasterServiceImpl.java @@ -228,6 +228,21 @@ public List getFrequency() { public List getLanguageByUserId(Integer userId) throws ECDException { return v_GetUserlangmappingRepo.findByUserId(userId); } + + public List getUserRoles(Long userId) { + if (null == userId || userId <= 0) { + throw new IllegalArgumentException("Invalid User ID : " + userId); + } + try { + List role = roleRepo.getRoleNamebyUserId(userId); + if (null == role || role.isEmpty()) { + throw new ECDException("No role found for userId : " + userId); + } + return role; + } catch (Exception e) { + throw new ECDException("Failed to retrieverole for usedId : " + userId + " error : " + e.getMessage()); + } + } //gender master diff --git a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..c3fde93 --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAccessDeniedHandler.java @@ -0,0 +1,28 @@ +package com.iemr.ecd.utils.advice.exception_handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.Map; + +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private static final ObjectMapper mapper = new ObjectMapper(); + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 + response.setContentType("application/json"); + Map errorResponse = Map.of("error" , "Forbidden", + "message","Access denied"); + response.getWriter().write(mapper.writeValueAsString(errorResponse)); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..85488ae --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/advice/exception_handler/CustomAuthenticationEntryPoint.java @@ -0,0 +1,23 @@ +package com.iemr.ecd.utils.advice.exception_handler; + +import java.io.IOException; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}"); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java b/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java index 719e259..e0b9738 100644 --- a/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java +++ b/src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java @@ -59,7 +59,7 @@ public T extractClaim(String token, Function claimsResolver) { return claims != null ? claimsResolver.apply(claims) : null; } - private Claims extractAllClaims(String token) { + public Claims extractAllClaims(String token) { return Jwts.parser() .verifyWith(getSigningKey()) .build() diff --git a/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java new file mode 100644 index 0000000..8fadd88 --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/mapper/RoleAuthenticationFilter.java @@ -0,0 +1,98 @@ +package com.iemr.ecd.utils.mapper; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.iemr.ecd.service.masters.MasterServiceImpl; +import com.iemr.ecd.utils.constants.Constants; +import com.iemr.ecd.utils.redis.RedisStorage; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +@Component +public class RoleAuthenticationFilter extends OncePerRequestFilter { + Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private RedisStorage redisService; + + @Autowired + private MasterServiceImpl userService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException, java.io.IOException { + List authRoles = null; + try { + String jwtFromCookie = CookieUtil.getJwtTokenFromCookie(request); + String jwtFromHeader = request.getHeader(Constants.JWT_TOKEN); + + String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; + if(null == jwtToken || jwtToken.trim().isEmpty()) { + filterChain.doFilter(request, response); + return; + } + Claims extractAllClaims = jwtUtil.extractAllClaims(jwtToken); + if(null == extractAllClaims) { + filterChain.doFilter(request, response); + return; + } + Object userIdObj = extractAllClaims.get("userId"); + String userId = userIdObj != null ? userIdObj.toString() : null; + if (null == userId || userId.trim().isEmpty()) { + filterChain.doFilter(request, response); + return; + } + Long userIdLong; + try { + userIdLong=Long.valueOf(userId); + }catch (NumberFormatException ex) { + logger.warn("Invalid userId format: {}",userId); + filterChain.doFilter(request, response); + return; + } + authRoles = redisService.getUserRoleFromCache(userIdLong); + if (authRoles == null || authRoles.isEmpty()) { + List roles = userService.getUserRoles(Long.valueOf(userId)); // assuming this returns multiple roles + authRoles = roles.stream() + .filter(Objects::nonNull) + .map(String::trim) + .map(role -> "ROLE_" + role.toUpperCase().replace(" ", "_")) + .collect(Collectors.toList()); + redisService.cacheUserRoles(Long.valueOf(userId), authRoles); + } + + List authorities = authRoles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null, authorities); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (Exception e) { + logger.error("Authentication filter error for request {}: {}", request.getRequestURI(), e.getMessage()); + SecurityContextHolder.clearContext(); + } finally { + filterChain.doFilter(request, response); + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java new file mode 100644 index 0000000..31409fa --- /dev/null +++ b/src/main/java/com/iemr/ecd/utils/mapper/SecurityConfig.java @@ -0,0 +1,52 @@ +package com.iemr.ecd.utils.mapper; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; + +import com.iemr.ecd.utils.advice.exception_handler.CustomAccessDeniedHandler; +import com.iemr.ecd.utils.advice.exception_handler.CustomAuthenticationEntryPoint; + +@Configuration +@EnableMethodSecurity +@EnableWebSecurity +public class SecurityConfig { + private final RoleAuthenticationFilter roleAuthenticationFilter; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; + + public SecurityConfig(RoleAuthenticationFilter roleAuthenticationFilter, + CustomAuthenticationEntryPoint customAuthenticationEntryPoint, + CustomAccessDeniedHandler customAccessDeniedHandler) { + this.roleAuthenticationFilter = roleAuthenticationFilter; + this.customAuthenticationEntryPoint = customAuthenticationEntryPoint; + this.customAccessDeniedHandler = customAccessDeniedHandler; + } + +@Bean +public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + CookieCsrfTokenRepository csrfTokenRepository = new CookieCsrfTokenRepository(); + csrfTokenRepository.setCookieHttpOnly(true); + csrfTokenRepository.setCookiePath("/"); + http + .csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/*").permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + ) + .addFilterBefore(roleAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); +} +} diff --git a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java index d795ad7..99370de 100644 --- a/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java +++ b/src/main/java/com/iemr/ecd/utils/redis/RedisStorage.java @@ -21,6 +21,9 @@ */ package com.iemr.ecd.utils.redis; +import java.time.Duration; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +31,7 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.types.Expiration; import org.springframework.stereotype.Component; @@ -94,4 +98,26 @@ public void updateConcurrentSessionObject(String value) { } } + @Autowired + private RedisTemplate redisTemplate; + + public void cacheUserRoles(Long userId, List roles) { + try { + String key = "roles:" + userId; + redisTemplate.delete(key); // Clear previous cache + redisTemplate.opsForList().rightPushAll(key, roles); + } catch (Exception e) { + logger.warn("Failed to cache role for user {} : {} ", userId, e.getMessage()); + } + + } + + public List getUserRoleFromCache(Long userId) { + try { + return redisTemplate.opsForList().range("roles:" + userId, 0, -1); + } catch (Exception e) { + logger.warn("Failed to retrieve cached role for user {} : {} ", userId, e.getMessage()); + return null; + } + } }