В 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:
CHANNEL_FILTER
CONCURRENT_SESSION_FILTER
SESSION_CONTEXT_INTEGRATION_FILTER
LOGOUT_FILTER
...
то мы увидим, что фильтр 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 не совпадают.
Отправить комментарий