Показаны сообщения с ярлыком bug. Показать все сообщения
Показаны сообщения с ярлыком bug. Показать все сообщения

понедельник, 2 февраля 2009 г.

Проблема с SecurityContext-ом при invalidate http-сессии

В Spring Security есть возможность определения допустимого числа параллельных http-сессий от одного пользователя. Как только кол-во свободных сессий исчерпает себя, то по-умолчанию самая первая из них будет уничтожена через метод invalidate() и ее место займет новая. За настройку такой возможности отвечает тэг concurrent-session-control и его параметр max-sessions.

Проблема, которая была обнаружена мной буквально на днях, следующая - при уничтожении сессии, вместо объекта org.springframework.security.context.SecurityContext приходит null. Выявить эту ошибку может следующий код:

public void onApplicationEvent(ApplicationEvent event) {
if (!(event instanceof HttpSessionDestroyedEvent)) {
return;
}

SecurityContext context = SecurityContextHolder.getContext();
Object principal = context.getAuthentication().getPrincipal();

...

}

Если посмотреть порядок следования фильтров в Spring Security:

  1. CHANNEL_FILTER

  2. CONCURRENT_SESSION_FILTER

  3. SESSION_CONTEXT_INTEGRATION_FILTER

  4. LOGOUT_FILTER

  5. ...

то мы увидим, что фильтр SESSION_CONTEXT_INTEGRATION_FILTER, который как раз и выставляет нужный нам объект SecurityContext, находится после CONCURRENT_SESSION_FILTER фильтра, управляющего параллельными сессиями. Такая "неверная", в данном случае, конфигурация фильтров и приводит к потере SecurityContext-а. Потому завел на это дело багу - SEC-1092. Решения я увидел два - поменять фильтры местами, с помощью конфигурации это делается довольно просто, и выставить объект SecurityContext самому. Второй вариант мне показался более верным. Менять фильтры местами, думаю не стоит, возможно есть причина по которой они расположены именно так.

Нужный нам экземпляр SecurityContext-а можно взять из текущий http-сессии, к которой он и привязан. Хранится он в сессии по имени HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY. Объект класса HttpSessionContextIntegrationFilter как раз и находится на позиции SESSION_CONTEXT_INTEGRATION_FILTER, и выставляет текущий SecurityContext в SecurityContextHolder. Сам код решения взят из этого фильтра и выглядит так:

public void onApplicationEvent(ApplicationEvent event) {
if (!(event instanceof HttpSessionDestroyedEvent)) {
return;
}

HttpSession session = event.getSession();
SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(context);


SecurityContext context = SecurityContextHolder.getContext();
Object principal = context.getAuthentication().getPrincipal();

...

}

среда, 22 октября 2008 г.

Проблема логина через https в Spring Security 2.0

Довольно тривиальная задача, как мне казалось, сделать логин на https-соединении с дальнейшим редиректом на страницу доступную по обычному http-каналу. Для реализации такого решения использовался Spring Security 2.0.4. Однако, все оказалось не так просто.
Для начала приведу свою настройку security-контекста:

<sec:http auto-config="true">
<sec:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
<sec:intercept-url pattern="/index.jsp" access="ROLE_PLAYER" requires-channel="http"/>
<sec:form-login login-page="/login.jsp" default-target-url="/index.jsp" />
</sec:http>

Конфигурация правильная, только вот редирект не работает. Проблема в том, что после успешной аутентификации, меня перебрасывает обратно на login.jsp.

Воспользовашись HttpWatch-ем, я выяснил, что проблема в том, что при переходе, допустим, с линка https://localhost:8443/login.jsp на линк http://localhost:8080/index.jsp, jsessionid передавалась в куке через http-заголовок, а не через урл, как полагается в этом случае. Поэтому Spring Security не видел ранее созданной сесси и создавал свою. Кстати, по умолчанию, после успешной аутентификации он создает новую сессию, дабы предотвратить атаку Session Fixation.

Я завел на это багу http://jira.springframework.org/browse/SEC-1019. Не знаю, когда она будет закрыта, думаю, что не скоро. Поэтому могу предложить свое, временное, решение этой проблемы.
В конфигурации security-контекста пишем:

<sec:http entry-point-ref="authEntryPoint">
<sec:anonymous/>
<sec:port-mappings>
<sec:port-mapping http="8080" https="8443"/>
</sec:port-mappings>
<sec:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
<sec:intercept-url pattern="/index.jsp" access="ROLE_PLAYER" requires-channel="http"/>
</sec:http>

<bean id="authEntryPoint" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>

<bean id="authFilter" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
<sec:custom-filter position="AUTHENTICATION_PROCESSING_FILTER" />
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="defaultTargetUrl" value="/"/>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="targetUrlResolver">
<bean class="ru.someone.security.ExTargetUrlResolverImpl">
<property name="url" value="/index.jsp"/>
</bean>
</property>
</bean>

<sec:authentication-manager alias="authenticationManager"/>

Столько xml-я пришлось написать лишь для того, чтобы использовать свою реализацию org.springframework.security.ui.TargetUrlResolver, по-другому никак. Вот собственно и сама реализация этого интерфейса:

package ru.someone.security;

import javax.servlet.http.HttpServletRequest;
import org.springframework.security.Authentication;
import org.springframework.security.ui.TargetUrlResolverImpl;
import org.springframework.security.ui.savedrequest.SavedRequest;

public class ExTargetUrlResolverImpl extends TargetUrlResolverImpl {

private String url;

@Override
public String determineTargetUrl(SavedRequest savedRequest, HttpServletRequest request,
Authentication auth) {
String sessionId = request.getSession().getId();
return url + ";jsessionid=" + sessionId;
}

public void setUrl(String url) {
this.url = url;
}
}