Централизованная система управления сеансами (и уничтожения) для Spring Security и / или Spring BlazeDS Integration
Мне трудно реализовать функцию, которую запрашивает наш клиент. Короче говоря, они хотят иметь возможность выйти из любого клиента по своему выбору из приложения через сторону администратора. Приложение использует Flex в качестве интерфейсной технологии и получает доступ к серверу через AMF. На стороне сервера используется Spring Security и Spring BlazeDS Integration.
В основном вопрос заключается в следующем: предлагает ли Spring Security и / или Spring BlazeDS Integration какую-либо централизованную систему для управления сеансами (и уничтожения) из коробки?
В целях проверки концепции я попытался выйти из системы всех пользователей и завершить все сеансы с помощью следующего кода:
package xxx.xxx.xxx;
import java.util.List;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.User;
import flex.messaging.MessageBroker;
import flex.messaging.security.LoginCommand;
public class SessionServiceImpl {
private static final Log log = LogFactory.getLog(SessionServiceImpl.class);
private SessionRegistry sessionRegistry;
private MessageBroker messageBroker;
public SessionRegistry getSessionRegistry() {
return sessionRegistry;
}
@Autowired
public void setSessionRegistry(SessionRegistry sessionRegistry) {
log.debug("sessionregistry set");
this.sessionRegistry = sessionRegistry;
}
public MessageBroker getMessageBroker() {
return messageBroker;
}
@Autowired
public void setMessageBroker(MessageBroker messageBroker) {
log.debug("messagebroker set");
this.messageBroker = messageBroker;
}
public void logoutUser(String userName) {
log.debug("Logging out user by username: "+userName);
List<Object> principals = null;
if(sessionRegistry != null){
principals = sessionRegistry.getAllPrincipals();
}else{
log.debug("sessionRegistry null");
}
if(principals != null){
for (Object object : principals) {
User user = (User)object;
// get single users all sessions
List<SessionInformation> sessions = sessionRegistry.getAllSessions(user, false);
log.debug("Sessions list size: "+sessions.size());
if(messageBroker != null){
LoginCommand command = messageBroker.getLoginManager().getLoginCommand();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, user.getPassword());
command.logout(usernamePasswordAuthenticationToken);
for (SessionInformation sessionInformation : sessions) {
log.debug(ReflectionToStringBuilder.toString(sessionInformation));
sessionInformation.expireNow();
sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
}
}else{
log.debug("messageBroker null");
}
if(object != null){
log.debug(ReflectionToStringBuilder.toString(object));
}else{
log.debug("object null");
}
}
}else{
log.debug("principals null");
}
}
}
К сожалению, приведенный выше код не работает. Насколько я могу сказать, это потому, что две вещи:
A) LoginCommand не является "общедоступным", но привязан к текущему сеансу, поэтому он попытается выйти из системы только текущего сеанса (сеанса, который использует администратор) и не замечает других сеансов
B) sessionInformation.expireNow() пытается завершить сеанс, но если пользователю удастся сделать запрос до того, как сеанс будет признан недействительным, сеанс не будет уничтожен
Из документации я вижу, что session.invalidate() может напрямую утратить сеанс, но, похоже, у меня нет возможности получить доступ ко всем объектам сеанса.
Какой самый быстрый или умный способ реализовать такую функцию?
С наилучшими пожеланиями, Юкка
2 ответа
Мой подход заключается в том, чтобы иметь косвенную недействительность сессии.
Используйте параметр безопасности ConcurrentSessionControl, чтобы ограничить 1 сеанс на пользователя. Напишите пользовательский SessionAuthenticationStrategy, который проверяет, был ли пользователь отмечен, и делает недействительным сеанс, если это необходимо. Обратите внимание, что стратегия сеанса должна быть выполнена до того, как, например, фильтр usernamepassword создаст новый сеанс.
Вы можете использовать базу данных или что-то вроде статического класса для хранения имен пользователей. Кроме того, вы, вероятно, захотите иметь какую-то временную метку, которая делает недействительными сессии только через x минут после того, как они были отмечены.
Совсем другой подход заключается в реализации слушателя сеанса сервлета, который записывает все сеансы входа в систему в пользовательской карте сеансов и может при необходимости аннулировать их.
Вы можете взглянуть на справочное руководство о том, как подключить компоненты http://docs.spring.io/spring-security/site/docs/3.0.x/reference/session-mgmt.html
Я провел много времени, пытаясь достичь того же.
В конце концов, я думаю, что решил это. Сначала удалите раздел кода LoginCommand вашего кода, поскольку, как вы предполагаете, он относится к пользователю, инициирующему удаление (в вашем случае, к администратору), а не к сеансу целевого пользователя.
Затем попробуйте удалить это:
sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
По некоторым причинам это, по-видимому, отменяет истечение срока действия без фактической остановки дальнейших запросов. Невпечатляющих!
Если это не сработает, то в середине соприкосновения с этим у меня была другая идея, которую я не осуществил. Это добавить фильтр для каждого запроса и проверить срок действия сессии.
Итак, в вашей безопасности XML:
<beans:bean id="sessionFilter" class="my.package.CustomSessionFilter"/>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<http use-expressions="true" auto-config="true">
<custom-filter after="CONCURRENT_SESSION_FILTER" ref="sessionFilter"/>
<session-management>
<concurrency-control session-registry-ref="sessionRegistry"/>
</session-management>
...
а потом класс:
@Component
public class CustomSessionFilter extends OncePerRequestFilter
{
/**The session registry.*/
@Autowired
private SessionRegistry sessionRegistry;
@Override
protected void doFilterInternal( HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain )
throws ServletException,
IOException
{
//get session for the user, and if expired, do something (e.g. redirect)
//else...
filterChain.doFilter(request, response);
}
}
Я обнаружил, что это успешно вызывается на основе запроса. Надеюсь, вам не нужно вышеуказанное, так как это неэффективный способ достичь того, что должна сделать для вас структура.