Настройка обратного прокси-сервера Apache для сайта, имеющего Spring Security
У меня есть приложение Spring MVC, которое использует Spring Security для входа в систему. Я использую Apache Webserver в качестве прокси и 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 для этого?
3 ответа
Я боролся с этой же проблемой в течение нескольких дней, и я мог бы ее решить. Я новичок в 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/
Внешний клиент запрашивает защищенный URL: http:// www.example.com/secret
GET /secret HTTP/1.1
Apache передает это по адресу http:// localhost:8080/webapp/secret
Один из фильтров безопасности Spring вмешивается и отвечает перенаправлением на / login
HTTP/1.1 302 Found Location: http://www.example.com/login
Браузер выбирает URL
GET /login HTTP/1.1
Apache передает это по адресу http:// localhost: 8080 / webapp / login
Spring отвечает своей страницей входа по умолчанию
HTTP/1.1 200 OK
Интересно отметить, что форма входа в систему, созданная 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-адреса, прежде чем он достигнет прокси-сервера, который снова добавит его. Apache RewriteRule, кажется, делает свое дело:
RewriteRule /webapp/(.*)$ http://localhost:8080/webapp/$1 [P]
Хотя это позволило устранить ошибки 404, и я увидел, что Apache теперь проксирует правильный URL-адрес, но при каждом входе в систему я постоянно отображал страницу входа в систему. Кажется, этот следующий бит конфигурации решает эту проблему:
ProxyPassReverseCookieDomain localhost www.example.com
ProxyPassReverseCookiePath /webapp/ /
Я полагаю, что это может быть связано с тем, что прокси-сервер приводил к неправильной настройке домена и пути в файле cookie, но я должен прочитать об этом подробнее!
Я надеюсь, что это поможет кому-то еще, и люди с большим опытом, чем я, могут прокомментировать, является ли это справедливым решением...
Это возможно, если вы определите свой собственный Filter & 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 здесь