Giải quyết vấn đề ContextPath, RequestURI, getScheme và ServerPort không chính xác khi sử dụng Nginx làm reverse proxy cho Tomcat/Spring Boot

Vấn đề gặp phải

Ví dụ về proxy từ /amp đến /crm
Địa chỉ yêu cầu:
Thông tin in ra (đã bỏ qua phần giải quyết dấu gạch chéo bị trùng)

Có thể thấy contextPath là /crm của Tomcat thay vì /amp được yêu cầu bởi trình duyệt. Điều này có thể dẫn đến:

  • Lỗi 404 do công cụ template (JSP, Thymeleaf)拼接 sai thông tin header
  • Lỗi 404 khi thực hiện Redirect từ phía backend

Giải pháp

====

Tạo lớp kế thừa HttpServletRequestWrapper, ghi đè các phương thức getContextPath, getRequestURI, getRequestURL, getScheme, getServerPort (hai phương thức sau để giải quyết vấn đề khi Nginx proxy qua HTTPS)

Cấu hình Nginx

       location /amp {
            proxy_pass  http://127.0.0.1:8081/crm/;
            proxy_set_header   Host             $host;
            proxy_set_header   Port             $server_port; # để sử dụng trong code Java
            proxy_set_header   Request-URI      $request_uri; # để sử dụng trong code Java
            proxy_set_header   Context-Path     /amp; # để sử dụng trong code Java
            proxy_cookie_path  /crm             /amp;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme; # để sử dụng trong code Java
        }

Lớ HttpServletRequestWrapper tùy chỉnh

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;

/**
 * Lớp wrapper để sửa chữa contextPath sau khi được Nginx chuyển tiếp
 */
public class CustomRequestWrapper extends HttpServletRequestWrapper {

    private final String originalContextPath;
    private final String originalRequestURI;

    public CustomRequestWrapper(HttpServletRequest request) {
        super(request);
        this.originalContextPath = request.getContextPath();
        this.originalRequestURI = request.getRequestURI();
    }

    @Override
    public String getContextPath() {
        String unknown = "unknown";
        // Ưu tiên lấy giá trị từ header được Nginx thiết lập
        String contextPath = super.getContextPath();
        String temp = getHeader("Context-Path");
        if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
            contextPath = temp;
        }
        return contextPath;
    }

    @Override
    public String getRequestURI() {
        // Thay thế phần oldContextPath bằng contextPath mới
        String requestURI = super.getRequestURI();
        if (requestURI.startsWith(originalContextPath)) {
            requestURI = getContextPath() + requestURI.substring(originalContextPath.length());
        }
        return requestURI;
    }

    @Override
    public StringBuffer getRequestURL() {
        // Thay thế phần oldRequestURI trong RequestURL bằng URI mới
        StringBuffer requestURL = super.getRequestURL();
        String urlStr = requestURL.toString();
        if (urlStr.endsWith(originalRequestURI)) {
            requestURL.replace(
                    urlStr.indexOf(originalRequestURI), 
                    urlStr.indexOf(originalRequestURI) + originalRequestURI.length(),
                    getRequestURI());
        }
        return requestURL;
    }

    @Override
    public String getScheme() {
        String unknown = "unknown";
        // Ưu tiên lấy giá trị từ header được Nginx thiết lập
        String scheme = super.getScheme();
        String temp = getHeader("X-Forwarded-Proto");
        if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
            scheme = temp;
        }
        return scheme;
    }

    @Override
    public int getServerPort() {
        String unknown = "unknown";
        // Ưu tiên lấy giá trị từ header được Nginx thiết lập
        int serverPort = super.getServerPort();
        String temp = getHeader("Port");
        if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
            serverPort = Integer.parseInt(temp);
        }
        return serverPort;
    }
}

Tạo Filter để áp dụng wrapper cho request


import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Filter để thay thế request object khi sử dụng Nginx làm reverse proxy
 */
@Component
public class NginxProxyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        CustomRequestWrapper customRequest = new CustomRequestWrapper((HttpServletRequest) request);
        chain.doFilter(customRequest, response);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

Kết quả

Thẻ: nginx Tomcat spring-boot reverse-proxy servlet-filter

Đăng vào ngày 26 tháng 5 lúc 01:42