3

我很难实现客户要求的功能。简而言之,他们希望能够通过管理端将他们选择的任何客户从应用程序中注销。该应用程序使用 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() 可以直接使会话无效,但似乎我无法访问所有会话对象。

实现此类功能的最快或最聪明的方法是什么?

最好的问候, 朱卡

4

2 回答 2

0

我的方法是间接会话失效。

使用 ConcurrentSessionControl 安全选项限制每个用户 1 个会话。编写一个自定义 SessionAuthenticationStrategy 来检查用户是否已被标记并在需要时使会话无效。请注意,会话策略应在例如 usernamepassword 过滤器创建新会话之前执行。

您可以使用数据库或静态类之类的东西来保存用户名。此外,您可能还希望有某种时间戳,它只会在会话被标记后 x 分钟使会话无效。

另一种方法是实现一个 servlet 会话侦听器,它将所有登录会话记录在用户->会话映射中,并在必要时使它们无效

您可以查看有关如何连接 bean http://docs.spring.io/spring-security/site/docs/3.0.x/reference/session-mgmt.html的参考手册

于 2013-10-07T16:19:56.103 回答
0

我花了很长时间试图实现同样的目标。

最后,我想我已经解决了。首先删除代码的 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);
}
}

我发现这是根据每个请求成功调用的。希望您不需要上述内容,因为这是一种不优雅的方式来实现框架应该为您做的事情。

于 2013-11-29T11:40:32.380 回答