четверг, 2 апреля 2009 г.

Переключение из HTTPS-соединения в HTTP (в Tomcat)

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

К примеру, если сделать на HTTPS-соединении только логин пользователя, а после его входа в систему снова использовать обычный HTTP, то на web-сервере Tomcat придется реализовать поддержку корректного переключения с HTTPS на HTTP.

Конфигурация Spring Security для такого случая:

<security:http auto-config="true" >
<security:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https"/>
<security:intercept-url pattern="/billing.html" access="ROLE_USER"
requires-channel="http"/>
</security:http>

Возможны два варианта переключения соединений:


1.    2.

В первом случае все будет работать, т.к. Tomcat создаст cookie сперва для HTTP сессии, которая будет действительна и для HTTPS, а следовательно может быть аутентифицирована Spring Security и использована дальше в работе с обычным HTTP.

Во втором случае Tomcat создаст cookie для HTTPS с параметром secure, а это значит что аутентификация такой сессии не позволит использовать в дальнейшем обычную HTTP сессию. Сookie с параметром secure привязана лишь к HTTPS-сессиям. Для пользователя же это обернется редиректом Spring Security на страницу логина, но с уже созданной cookie, и только второй логин уже пустит пользователя в систему. Чтобы решить эту проблему необходимо, находясь в HTTPS сессии, создать cookie для обычной HTTP сессии без параметра secure. С помощью фильтра это реализовать довольно просто:

package com.blogspot.nkoksharov.filter;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.lang.StringUtils;

public class HttpsCookieFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
final HttpSession session = httpRequest.getSession(false);

if (request.isSecure() && session != null) {
final Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setMaxAge(-1);
sessionCookie.setSecure(false);
String contextPath = httpRequest.getContextPath();
if (StringUtils.isNotBlank(contextPath)) {
sessionCookie.setPath(contextPath);
} else {
sessionCookie.setPath("/");
}
httpResponse.addCookie(sessionCookie);
}

chain.doFilter(request, response);
}

...

}

Теперь переход с https-соединения будет выглядеть так:

Фильтр лучше всего поместить в самом начале цепочки фильтров, важно чтобы он был перед springSecurityFilterChain. Следует отметить, что при таком решении http-cookie будет приходить пользователю на каждый https-запрос. Т.к. сложно опеределить при https-соединении создана ли cookie для http-канала.

3 комментария:

i_home комментирует...

Спасибо за заметку, как раз искал информацию по использованию фильтров с cookie и сессиями, и нашел ваш блог. Все понятно и в тему.

Анонимный комментирует...

Спасибо большущщее!!!! :))) Пол дня протрахался с этим безобразием. Извините за мой французский...
//Даниил

Andrew Beletsky комментирует...

Пост действительно очень хороший. Но здесь было бы неплохо описать ситуацию, когда cookies отключены.