用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统
Posted
技术标签:
【中文标题】用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统【英文标题】:Centralized system for session management (and killing) for Spring Security and/or Spring BlazeDS Integration 【发布时间】:2013-10-14 04:06:01 【问题描述】:我很难实现客户要求的功能。简而言之,他们希望能够通过管理端将他们选择的任何客户从应用程序中注销。该应用程序使用 Flex 作为前端技术并通过 AMF 访问服务器。服务器端正在使用 Spring Security 和 Spring BlazeDS 集成。
基本上问题是: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() 可以直接使会话无效,但似乎我无法访问所有会话对象。
实现此类功能的最快或最聪明的方法是什么?
最好的问候, 尤卡
【问题讨论】:
【参考方案1】:我的方法是让间接会话失效。
使用 ConcurrentSessionControl 安全选项限制每个用户 1 个会话。 编写一个自定义 SessionAuthenticationStrategy 来检查用户是否已被标记并在需要时使会话无效。 请注意,会话策略应在用户名密码过滤器创建新会话之前执行。
您可以使用数据库或静态类之类的东西来保存用户名。 此外,您可能还希望有某种时间戳,它只会在会话被标记后 x 分钟使会话无效。
另一种方法是实现一个 servlet 会话侦听器,它将所有登录会话记录在用户->会话映射中,并在必要时使它们无效
您可以查看有关如何连接 bean 的参考手册 http://docs.spring.io/spring-security/site/docs/3.0.x/reference/session-mgmt.html
【讨论】:
除非我理解错了,否则这不起作用,因为SessionAuthenticationStrategy
仅在登录时使用,而不是在后续请求中使用。【参考方案2】:
我花了很长时间试图实现同样的目标。
最后,我想我已经解决了。首先删除代码的 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);
我发现它可以在每个请求的基础上成功调用。希望您不需要上述内容,因为这是一种不优雅的方式来实现框架应该为您做的事情。
【讨论】:
以上是关于用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Security SAML 示例中配置 IDP 元数据和 SP 元数据?
用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统
生成 SP 元数据时出现意外的堆栈跟踪表单 Spring-Security-SAML?
使用 Spring Security saml 的 IDP 会话超时