Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/environment/common_ci.properties
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,6 @@ logging.level.org.springframework=INFO
logging.path=logs/
[email protected]_API_LOGGING_FILE_NAME@
[email protected]_SECRET_KEY@

[email protected]_DOC_ENABLED@
[email protected]_DOC_ENABLED@
75 changes: 49 additions & 26 deletions src/main/java/com/wipro/fhir/utils/JwtUserIdValidationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.wipro.fhir.utils.http.AuthorizationHeaderRequestWrapper;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -29,6 +31,7 @@ public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil) {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;

HttpServletResponse response = (HttpServletResponse) servletResponse;

String path = request.getRequestURI();
Expand All @@ -47,49 +50,69 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
} else {
logger.info("No cookies found in the request");
}

// Log headers for debugging
String jwtTokenFromHeader = request.getHeader("Jwttoken");
logger.info("JWT token from header: ");


// Skip login and public endpoints
if (path.equals(contextPath + "/user/userAuthenticate")
|| path.equalsIgnoreCase(contextPath + "/user/logOutUserFromConcurrentSession")
|| path.startsWith(contextPath + "/swagger-ui")
|| path.startsWith(contextPath + "/v3/api-docs")
|| path.startsWith(contextPath + "/public")) {
logger.info("Skipping filter for path: " + path);
if (shouldSkipPath(path, contextPath)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}

try {
// Retrieve JWT token from cookies
String jwtTokenFromCookie = getJwtTokenFromCookies(request);
logger.info("JWT token from cookie: ");

// Determine which token (cookie or header) to validate
String jwtToken = jwtTokenFromCookie != null ? jwtTokenFromCookie : jwtTokenFromHeader;
if (jwtToken == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT token not found in cookies or headers");
return;
String jwtFromCookie = getJwtTokenFromCookies(request);
String jwtFromHeader = request.getHeader("JwtToken");
String authHeader = request.getHeader("Authorization");

if (jwtFromCookie != null) {
logger.info("Validating JWT token from cookie");
if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromCookie)) {
AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper(request, "");
filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse);
return;
}
}

// Validate JWT token and userId
boolean isValid = jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken);
if (jwtFromHeader != null) {
logger.info("Validating JWT token from header");
if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtFromHeader)) {
AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper(request, "");
filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse);
return;
}
}
String userAgent = request.getHeader("User-Agent");
logger.info("User-Agent: " + userAgent);

if (isValid) {
// If token is valid, allow the request to proceed
if (userAgent != null && isMobileClient(userAgent) && authHeader != null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token");
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mobile-client bypass may open an authentication hole

Right now any request whose User-Agent contains β€œokhttp” AND merely has an Authorization header (regardless of its content) skips JWT validation.
An attacker can spoof the UA string and send an empty or bogus header to gain access.

Consider tightening the rule:

  • Check that the header starts with Bearer and validate it, or
  • Restrict the bypass to very specific paths/methods, or
  • Remove the bypass and rely solely on proper token validation.
πŸ€– Prompt for AI Agents
In src/main/java/com/wipro/fhir/utils/JwtUserIdValidationFilter.java around
lines 85 to 88, the current logic allows requests with a User-Agent containing
"okhttp" and any Authorization header to bypass JWT validation, which is a
security risk. To fix this, modify the condition to check that the Authorization
header starts with "Bearer " and validate the token properly before allowing the
bypass, or alternatively restrict the bypass to specific paths or methods, or
remove the bypass entirely to enforce JWT validation on all requests.


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 boolean isMobileClient(String userAgent) {
if (userAgent == null)
return false;

userAgent = userAgent.toLowerCase();

return userAgent.contains("okhttp"); // iOS (custom clients)
}

private boolean shouldSkipPath(String path, String contextPath) {
return path.equals(contextPath + "/user/userAuthenticate")
|| path.equalsIgnoreCase(contextPath + "/user/logOutUserFromConcurrentSession")
|| path.startsWith(contextPath + "/swagger-ui")
|| path.startsWith(contextPath + "/v3/api-docs")
|| path.startsWith(contextPath + "/public");
}

private String getJwtTokenFromCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.wipro.fhir.utils.http;


import java.util.*;

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;
}

@Override
public String getHeader(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Authorization;
}
return super.getHeader(name);
}

@Override
public Enumeration<String> getHeaders(String name) {
if ("Authorization".equalsIgnoreCase(name)) {
return Collections.enumeration(Collections.singletonList(Authorization));
}
return super.getHeaders(name);
}

@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if (!names.contains("Authorization")) {
names.add("Authorization");
}
return Collections.enumeration(names);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
boolean status = true;
logger.debug("In preHandle we are Intercepting the Request");
String authorization = request.getHeader("Authorization");
if (authorization.equals("")) {
logger.info("Authorization header is null or empty. Skipping HTTPRequestInterceptor.");
return true; // Allow the request to proceed without validation
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Null-pointer risk when Authorization header is absent

authorization.equals("") throws an NPE if the header is missing.
A safe check protects all callers and keeps the log message aligned with reality.

-if (authorization.equals("")) {
+if (authorization == null || authorization.trim().isEmpty()) {
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (authorization.equals("")) {
logger.info("Authorization header is null or empty. Skipping HTTPRequestInterceptor.");
return true; // Allow the request to proceed without validation
}
// src/main/java/com/wipro/fhir/utils/http/HTTPRequestInterceptor.java
// Lines: ~62-65
if (authorization == null || authorization.trim().isEmpty()) {
logger.info("Authorization header is null or empty. Skipping HTTPRequestInterceptor.");
return true; // Allow the request to proceed without validation
}
πŸ€– Prompt for AI Agents
In src/main/java/com/wipro/fhir/utils/http/HTTPRequestInterceptor.java around
lines 62 to 65, the code calls authorization.equals("") without checking if
authorization is null, which can cause a NullPointerException if the
Authorization header is absent. Fix this by first checking if authorization is
null or empty using a safe method like authorization == null ||
authorization.isEmpty(), and update the log message to reflect that the header
may be null or empty.

logger.debug("RequestURI::" + request.getRequestURI() + " || Authorization ::" + authorization
+ " || method :: " + request.getMethod());
if (!request.getMethod().equalsIgnoreCase("OPTIONS")) {
Expand Down