Требовать HTTPS с Spring Security за обратным прокси

У меня есть приложение Spring MVC, защищенное с помощью Spring Security. Большинство приложений использует простой HTTP для экономии ресурсов, но небольшая часть обрабатывает более конфиденциальную информацию и требует HTTPS-канала.

Извлечь из security-config.xml:

<sec:http authentication-manager-ref="authenticationManager" ... >
    ...
    <sec:intercept-url pattern="/sec/**" requires-channel="https"/>
    <sec:intercept-url pattern="/**" requires-channel="http"/>
</sec:http>

Все работало нормально, пока мы не решили перенести его на основной сервер, где серверы приложений работают за обратными прокси-серверами. И поскольку теперь HTTPS обрабатывается обратными прокси-серверами, сервер приложений видит только HTTP-запросы и запрещает доступ к иерархии /sec/**.

После некоторых исследований я обнаружил, что прокси-серверы добавляют заголовок X-Forwarded-Proto: https (*) но в Spring Security HttpServletRequest.isSecure() используется для определения предлагаемой безопасности канала (извлечение из javadoc SecureChannelProcessor).

Как я могу сообщить Spring Security, что для защищенного запроса достаточно заголовка X-Forwarded-Proto: https?

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

В настоящее время я использую Spring Security 3.2 с XML-конфигурацией, но я готов принять ответы на основе Java-конфигурации и/или более поздней версии.

(*) Конечно, прокси удаляют заголовок, если он присутствовал во входящем запросе, поэтому приложение может быть в этом уверено.

Ответ 1

Если ваш сайт HTTPS и вы используете Apache Tomcat за другой системой, которая обрабатывает завершение TLS, вы можете сказать Tomcat "сделать вид", что он обрабатывает завершение TLS.

Это делает request.isSecure() return true;

Для этого вам нужно добавить secure="true" в вашу конфигурацию Connector в server.xml.

https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

Смотрите также атрибут scheme.

Ответ 2

Вид ответа на вопрос NeilMcGuigan, который показал, что решение было частью контейнера сервлета.

Tomcat еще лучше. Существует клапан, предназначенный для маскировки побочных эффектов обратного прокси. Выдержка из документации Tomcat для Удаленный IP-клапан:

Еще одна особенность этого клапана - заменить видимую схему (http/https), порт сервера и request.secure с помощью схемы, представленной прокси-сервером или балансировщиком нагрузки через заголовок запроса (например, "X-Forwarded-Proto" )).

Пример конфигурации клапана:

<Valve className="org.apache.catalina.valves.RemoteIpValve"
    internalProxies="192\.168\.0\.10|192\.168\.0\.11"
    remoteIpHeader="x-forwarded-for" proxiesHeader="x-forwarded-by"
    protocolHeader="x-forwarded-proto" />

Таким образом, при отсутствии другой конфигурации самого приложения вызов Request.isSecure() будет возвращать true, если запрос содержит поле заголовка X-Forwarded-Proto=https.

Я подумал о двух других возможностях, но окончательно предпочел бы, чтобы один:

  • используйте фильтр до Spring Безопасность ChannelProcessingFilter, чтобы обернуть запрос с помощью HttpServletRequestWrapper overriding isSecure() для обработки заголовка X-Forwarded-Proto - нужно написать и протестировать фильтр и обертку
  • используйте Spring BeanPostProcessor для поиска ChannelProcessingFilter и вручную добавьте ChannelDecisionManager, способный рассмотреть заголовок X-Forwarded-Proto - действительно слишком низкий уровень

Ответ 3

Spring Boot делает его очень простым (по крайней мере, со встроенным Tomcat).

1. Добавьте следующие строки в ваш application.properties:

server.use-forward-headers=true
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto

2. Сделайте следующий трюк с вашей конфигурацией HttpSecurity.

// final HttpSecurity http = ...
// Probably it will be in your 'WebSecurityConfigurerAdapter.configure()'

http.requiresChannel()
            .anyRequest().requiresSecure()

Источник - справочник Spring Boot

84.3 Включение HTTPS при работе за прокси-сервером