用于 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 会话超时

在期待多个应用程序时,我应该使用 Spring Security SAML 还是 Shiboleth SP?

用于非授权连接的 Spring Security REST Api