понедельник, 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();

...

}

1 комментарий:

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

SecurityContextHolder.getContext() приходит null потому что контекст в SecurityContextHolder (по умолчанию) хранится в ThreadLocal переменной, а тред выполняющий обработку события и тред к которому привязян context не совпадают.