diff --git a/src/main/java/com/iemr/mmu/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/mmu/utils/JwtUserIdValidationFilter.java index fbeab7ad..4be94681 100644 --- a/src/main/java/com/iemr/mmu/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/mmu/utils/JwtUserIdValidationFilter.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import com.iemr.mmu.utils.http.AuthorizationHeaderRequestWrapper; @@ -37,26 +36,56 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletResponse response = (HttpServletResponse) servletResponse; String origin = request.getHeader("Origin"); - - 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,serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); - response.setHeader("Vary", "Origin"); - response.setHeader("Access-Control-Allow-Credentials", "true"); + String method = request.getMethod(); + String uri = request.getRequestURI(); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Request Method: {}", method); + logger.debug("Request URI: {}", uri); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + if ("OPTIONS".equalsIgnoreCase(method)) { + if (origin == null) { + logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header"); + return; + } + if (!isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } } else { - logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); - } - - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - logger.info("OPTIONS request - skipping JWT validation"); - response.setStatus(HttpServletResponse.SC_OK); - return; + // For non-OPTIONS requests, validate origin if present + if (origin != null && !isOriginAllowed(origin)) { + logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed"); + return; + } } String path = request.getRequestURI(); String contextPath = request.getContextPath(); + // Set CORS headers and handle OPTIONS request only if origin is valid and + // allowed + if (origin != null && isOriginAllowed(origin)) { + addCorsHeaders(response, origin); + logger.info("Origin Validated | Origin: {} | Method: {} | URI: {}", origin, method, uri); + + if ("OPTIONS".equalsIgnoreCase(method)) { + // OPTIONS (preflight) - respond with full allowed methods + response.setStatus(HttpServletResponse.SC_OK); + return; + } + } else { + logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + + if ("OPTIONS".equalsIgnoreCase(method)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed for OPTIONS request"); + return; + } + } // Log cookies for debugging Cookie[] cookies = request.getCookies(); if (cookies != null) { @@ -126,6 +155,15 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization error: " + e.getMessage()); } } + + private void addCorsHeaders(HttpServletResponse response, String origin) { + response.setHeader("Access-Control-Allow-Origin", origin); // Never use wildcard + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Max-Age", "3600"); + } private boolean isOriginAllowed(String origin) { if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { @@ -139,8 +177,7 @@ private boolean isOriginAllowed(String origin) { String regex = pattern .replace(".", "\\.") .replace("*", ".*") - .replace("http://localhost:.*", "http://localhost:\\d+"); // special case for wildcard port - + .replace("http://localhost:.*", "http://localhost:\\d+"); boolean matched = origin.matches(regex); return matched; }); @@ -174,4 +211,4 @@ private void clearUserIdCookie(HttpServletResponse response) { cookie.setMaxAge(0); // Invalidate the cookie response.addCookie(cookie); } -} \ No newline at end of file +} diff --git a/src/main/java/com/iemr/mmu/utils/http/HttpInterceptor.java b/src/main/java/com/iemr/mmu/utils/http/HttpInterceptor.java index 9a5adeba..2eb39c9a 100644 --- a/src/main/java/com/iemr/mmu/utils/http/HttpInterceptor.java +++ b/src/main/java/com/iemr/mmu/utils/http/HttpInterceptor.java @@ -21,11 +21,14 @@ */ package com.iemr.mmu.utils.http; +import java.util.Arrays; + import javax.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; @@ -43,6 +46,9 @@ public class HttpInterceptor implements HandlerInterceptor { Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + @Value("${cors.allowed-origins}") + private String allowedOrigins; + @Autowired public void setValidator(Validator validator) { this.validator = validator; @@ -111,7 +117,14 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons response.setContentType(MediaType.APPLICATION_JSON); response.setContentLength(output.toString().length()); - response.setHeader("Access-Control-Allow-Origin", "*"); + + String origin = request.getHeader("Origin"); + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else if (origin != null) { + logger.warn("CORS headers NOT added for error response | Unauthorized origin: {}", origin); + } response.getOutputStream().print(output.toString()); status = false; @@ -141,4 +154,20 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse resp logger.debug("In afterCompletion Request Completed"); } + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + return false; + } + + return Arrays.stream(allowedOrigins.split(",")) + .map(String::trim) + .anyMatch(pattern -> { + String regex = pattern + .replace(".", "\\.") + .replace("*", ".*") + .replace("http://localhost:.*", "http://localhost:\\d+"); + return origin.matches(regex); + }); + } + }