Назад | Перейти на главную страницу

Настройка обратного прокси-сервера Apache для сайта с Spring Security

У меня есть приложение Spring MVC, которое использует Spring Security для входа в систему. Я использую веб-сервер Apache в качестве прокси и Tomcat. Ниже мой файл /etc/apache2/sites-enabled/example.com.conf:

ServerAdmin admin@example.com
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?

Я боролся с этой же проблемой в течение нескольких дней, и, возможно, я решил ее. Я новичок в Spring Security, так что не воспринимайте это как евангелие! Другие могут возразить ... Я использую Apache 2.4 (в OS X) и Spring Security 4.1.1.

Все отлично работало локально, но всякий раз, когда оно развертывалось для работы через обратный прокси, я получал 404 ошибки каждый раз, когда входил в систему. После долгих царапин в голове и поиска в Google вот что я обнаружил:

(Поскольку у меня недостаточно очков репутации, чтобы размещать более двух ссылок, мне пришлось использовать пробел после http: // для URL-адресов!)

Предположим, что Apache и Tomcat работают на одном и том же хосте (localhost) с Apache, настроенным на прокси-запросы от www.example.com к нашему веб-приложению, развернутому по контекстному пути '/ webapp'

    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"). Когда вы затем нажимаете кнопку отправки, выполняется POST для URL / webapp / login.

    POST /webapp/login HTTP/1.1
    

Теперь у нас есть проблема. Когда Apache передает это на внутренний сервер, результирующий URL-адрес будет http: // localhost / webapp / webapp / login. Вы можете увидеть это в журнале catalina.out, показывающем, что нет обработчика, который мог бы обработать запрос, поскольку путь контекста теперь дважды появляется в URL-адресе.

Проблема здесь в том, что директивы ProxyPass и ProxyReversePass (модуль mod_proxy) изменяют только заголовок HTTP Location, URL-адрес остается нетронутым. Что необходимо, так это удалить путь контекста из URL-адреса, прежде чем он достигнет прокси-сервера, который добавит его обратно. Апачи RewriteRule похоже, трюк:

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

Хотя это решило ошибки 404, и я мог видеть, что Apache теперь проксирует правильный URL-адрес, я постоянно получал повторное отображение страницы входа каждый раз, когда я входил в систему. Следующий фрагмент конфигурации, похоже, решает эту проблему:

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

Я считаю, что это может быть связано с тем, что проксирование приводило к неправильной установке домена и пути в файле cookie, но я должен прочитать об этом больше!

Я надеюсь, что это поможет кому-то еще, и люди с большим опытом, чем я, могут прокомментировать, является ли это справедливым решением ...

Это возможно, если вы определите свой собственный фильтр и HttpServletRequestWrapper со вставленным фильтром. перед фильтр Spring 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();
            }
        }
    }

А затем добавьте правильный фильтр перед весенний фильтр (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;

  }

Вы можете определить виртуальные хосты. Думаю, должно получиться что-то вроде этого:

<VirtualHost *:80>
    ServerAdmin for@get.it
    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 здесь