具有 Spring Security 的站点的 Apache 反向代理设置

具有 Spring Security 的站点的 Apache 反向代理设置

我有一个使用 Spring Security 进行登录的 Spring MVC 应用程序。我使用 Apache Webserver 作为代理和 Tomcat。以下是我的 /etc/apache2/sites-enabled/example.com.conf 文件:

ServerAdmin [email protected]
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html

ProxyPreserveHost On
ProxyRequests off

ProxyPass /myapp/j_spring_security_check http://XX.YY.ZZ.WW:8080/myapp/j_spring_security_check
ProxyPassReverse /myapp/j_spring_security_check http://XX.YY.ZZ.WW:8080/myapp/j_spring_security_check

ProxyPass /myapp http://XX.YY.ZZ.WW:8080/myapp
ProxyPassReverse /myapp http://XX.YY.ZZ.WW:8080/myapp

我的问题是现在我必须以以下方式访问我的网站:

www.example.com/myapp

我想以如下方式访问它

www.example.com

我尝试了一下,但登录却无法正常进行。我应该如何设置 ProxyPass 和 ProxyPassReverse?

答案1

我已经为这个问题苦苦挣扎了几天,我可能已经解决了它。我是 Spring Security 的新手,所以不要把这当作福音!其他人可能会反对...我正在使用 Apache 2.4(在 OS X 上)和 Spring Security 4.1.1。

一切都在本地运行良好,但每当它被部署到反向代理后面运行时,每次我登录时都会出现 404 错误。经过一番思考和谷歌搜索后,我发现了以下内容:

(由于我的信誉点数不足以发布超过 2 个链接,所以我不得不在 URL 后面使用“http://”后的空格!)

假设 Apache 和 Tomcat 在同一主机(localhost)上运行,并且 Apache 配置为将来自 www.example.com 的请求代理到我们在上下文路径 '/webapp' 下部署的 Web 应用程序

    ProxyPass / http://localhost:8080/webapp/
    ProxyPassReverse / http://localhost:8080/webapp/
  1. 外部客户端请求受保护的 URL:http:// www.example.com/secret

    GET /secret HTTP/1.1
    
  2. Apache 将其代理到 http:// localhost:8080/webapp/secret

  3. Spring 的一个安全过滤器进行干预,并通过重定向到 /login 进行响应

    HTTP/1.1 302 Found
    Location: http://www.example.com/login
    
  4. 浏览器获取 URL

    GET /login HTTP/1.1
    
  5. Apache 将其代理到 http:// localhost:8080/webapp/login

  6. Spring 使用其默认登录页面进行响应

    HTTP/1.1 200 OK
    
  7. 此时需要注意的有趣事情是,Spring 生成的登录表单在表单操作元素前添加了上下文路径前缀(即 action="/webapp/login")。当您单击提交按钮时,将对 URL /webapp/login 执行 POST

    POST /webapp/login HTTP/1.1
    

现在我们遇到了一个问题。当 Apache 将此代理到后端服务器时,生成的 URL 将为 http:// localhost/webapp/webapp/login。您可以在 catalina.out 日志中看到这一点,表明没有可以处理该请求的处理程序,因为上下文路径现在在 URL 中出现了两次。

这里的问题是 ProxyPass 和 ProxyReversePass 指令(mod_proxy 模块)只修改了 HTTP Location 标头,URL 保持不变。需要做的是在 URL 到达代理之前将其上下文路径从 URL 中剥离,代理会将其重新添加。Apache 的重写规则似乎可以解决问题:

RewriteRule /webapp/(.*)$ http://localhost:8080/webapp/$1 [P]

虽然这解决了 404 错误,而且我可以看到 Apache 现在代理到了正确的 URL,但每次登录时,我都会不断重新显示登录页面。接下来的配置似乎可以解决这个问题:

ProxyPassReverseCookieDomain localhost www.example.com
ProxyPassReverseCookiePath /webapp/ /

我相信这可能是因为代理导致 cookie 中的域和路径设置不正确,但我必须阅读更多相关内容!

我希望这对其他人有所帮助,并且在这个领域比我更专业的人可以评论这是否是一个公平的解决方案......

答案2

您可以定义虚拟主机。我认为应该这样做:

<VirtualHost *:80>
    ServerAdmin [email protected]
    ProxyRequests Off
    ProxyPreserveHost On
    ProxyPass / http://localhost:8080/myapp connectiontimeout=5 timeout=30
    ProxyPassReverse / http://localhost:8080/myapp
    ServerName youname.it
</VirtualHost>

我在这里使用 Apache 2.4 运行了类似的设置

答案3

这是可能的,如果你定义自己的 Filter 和 HttpServletRequestWrapper,并插入 FilterSpring Security 过滤器。通过重写“getContextPath”以返回空字符串,您可以设置 nginx/apache 反向代理。(其基本概念来自:http://www.lacerta.be/d7/content/keeping-real-user-ip-java-web-apps-behind-nginx-proxy,但这并不涵盖上下文路径)

在你的依赖管理软件中,添加 Servlet API:

   <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>

(当然,这假设您将在 Tomcat/J2EE 容器中运行)。

然后,在您的项目或共享库中,定义两个类:

    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;

    public class RealIPFilter implements Filter {

         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

             if (request instanceof HttpServletRequest) {
                chain.doFilter(new RealIPWrapper((HttpServletRequest)request), response);
             } else {
                chain.doFilter(request, response);
             }
         }

         @Override
         public void destroy() {}

         @Override
         public void init(FilterConfig config) throws ServletException {}

   }

然后是包装器:

    public class RealIPWrapper extends HttpServletRequestWrapper {

        public RealIPWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public String getContextPath() {
            return "";
        }

        @Override
        public String getRemoteAddr() {
            String realIP = super.getHeader("X-Real-IP");
            return realIP != null ? realIP : super.getRemoteAddr();
        }

        @Override
        public String getRemoteHost() {
            try {
                return InetAddress.getByName(this.getRemoteAddr()).getHostName();
            } catch (UnknownHostException|NullPointerException e) {
                return getRemoteAddr();
            }
        }
    }

然后添加正确的过滤器spring 过滤器(web.xml)

<filter>
    <filter-name>RealIPFilter</filter-name>
    <filter-class>RealIPFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>RealIPFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

那么在 NGINX 服务器块中:

  location / {
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $host;
     proxy_set_header X-NginX-Proxy true;

     proxy_cookie_path /<context-path>/ /;

     proxy_pass http://localhost:8080/<context>/;
     proxy_redirect off;

  }

相关内容