acegi流程分析之一《Acegi 中的HttpSessionEvent 监听机制 窥视Acegi

acegi流程分析之一《Acegi 中的HttpSessionEvent 监听机制 窥视Acegi

摘自:http://www.4ucode.com/Study/Topic/317797

 

Acegi 中的 HttpSessionEvent 监听机制 窥视 Acegi 的 工作流程。

 

Acegi 结合了 Spring 提供了不错的 HttpSessionEvent 监听机制

使用 Acegi 的 HttpSessionEvent 监听机制,我首先要在 web.xml 中增加一个 Listener

org.acegisecurity.ui.session.HttpSessionEventPublisher

有人要问这不是一个 Listener 么,类名应该是一个 *Listener 什么的啊。的确它实现了 javax.servlet.ServletContextListener 接口和 javax.servlet.http.HttpSessionListener接口。前者是在 web 应用创建时,可以可以通过 ServletContext 来获取一个 Spring 的 ApplicationContext 。后者是在 HttpSession 创建和超时时发布利用 Spring 的ApplicationContext. publishEvent() 方法发布事件的,注意并不时发布的 HttpSesionEvent ,而是有 HttpSession 作为构造参数,创建了 HttpSessionCreatedEvent 、HttpSessionDestroyedEvent ,这两个 Event 都是 Spring 的 ApplicationEvent 的子类,这样 Spring 才会处理这些 Event 。事件发布后, Spring 会在上下文查找实现了ApplicationListener 接口的接口子类来处理这两个 Event 的

 

到这,你心里或许有些疑惑, HttpSessionEvent 发布了,那个 ApplicationListener 来处理这个 HttpSessionEvent 呢?注意,重要人物出场了。

org.acegisecurity.concurrent. SessionRegistryImpl 出场了。看了它的源代码,你会发现它还实现另外一个接口 org.acegisecurity.concurrent. SessionRegistry ,

这个定义如下一些方法

 

public interface SessionRegistry {

    public Object[] getAllPrincipals();

    public SessionInformation[] getAllSessions(Object principal, boolean includeExpiredSessions);

    public SessionInformation getSessionInformation(String sessionId);

    public void refreshLastRequest(String sessionId);

    public void registerNewSession(String sessionId, Object principal)

        throws SessionAlreadyUsedException;

    public void removeSessionInformation(String sessionId);

}

这些方法的功能通过看方法名就能知道了。

看一下 SessionRegistryImpl. onApplicationEvent() 方法,看它做了些什么

 

    public void onApplicationEvent(ApplicationEvent event) {

        if (event instanceof HttpSessionDestroyedEvent) {

            String sessionId = ((HttpSession) event.getSource()).getId();

            removeSessionInformation(sessionId);

        }

    }

它怎么只处理 HttpSessionDestoryedEvent ,这就说 SessionRegistryImpl 只处理 HttpSession 的销毁,也就是这又是为什么呢。如果你仔细看了 SessionRegistry 的接口定义,或许能明白一些, SessionRegistryImpl 需要存储客户端用户的 SessionInformation , SessionInformation 里面又有什么, SesssionInformation 有四个字段

 

    private Date lastRequest;

    private Object principal;

    private String sessionId;

    private boolean expired = false;

除了 principal ,其他三个字段应该很容易理解,那这个 Principal 是什么东西呢, Principal 可以理解为当前客户端用户的身份信息,这个身份信息可以包含用户名,用户密码,用户在系统里面拥有的权限。 Acegi 提供了一个 UserDetail 来表示用户的身份。

 

public interface UserDetails extends Serializable {

    public GrantedAuthority[] getAuthorities();

    public String getPassword();

    public String getUsername();

    public boolean isAccountNonExpired();

     public boolean isAccountNonLocked();

    public boolean isCredentialsNonExpired();

    public boolean isEnabled();

}

顺便提一下 SessionRegistryImpl 处理 HttpSessionDestoryedEvent 是,只是把这个 HttpSesion 的有关信息也就是 SessionInformation 从存储中移出。

 

 

通过上面的分析,你觉得每当新的 HttpSession 创建时, SessionRegistryImpl 也处理这个 Event 还有意义吗?事实上 SessionRegistryImpl 没有处理HttpSessionCreatedEvent , Acegi 也没有提供其他的 Spring ApplicationListener 来处理这个 HttpSessionCreatedEvent 。

注意,我们的程序一般并不用直接与 SessionRegistryImpl 打交道,你只需在 Spring 的配置文件定义一个 Bean 就行了,如

 

< bean id = "sessionRegistry" class = "org.acegisecurity.concurrent.SessionRegistryImpl" />

只是如此而已。

 

那么当 HttpSession 创建时, Acegi 并不做处理,还会有其他的咚咚来处理么; SessionRegistryImpl 还需要存储用户的 Principal ,那么什么咚咚与SessionRegistryImpl 打交道呢?

有,但不过不是 ApplicationListener 了,别忘了, SpringSide 还定义了一系列的 Filter 让 Acegi 来做过滤(详情请见 org.springside.bookstore.plugins.security.applicationContext-security-acegi.xml ),那什么 Filter 与实现了 SessionRegistry 接口的 SessionRegistryImpl 打交道呢?下面我来介绍一下 ConcurrentSessionFilter,这个 Filter 是与 SessionRegistryImpl 打交道的。我们先看一下该 Filter 的 doFilter() 的方法 ( 截取了部分代码 ) 。

 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

        throws IOException, ServletException {

------------------

 

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        HttpServletResponse httpResponse = (HttpServletResponse) response;

 

        HttpSession session = httpRequest.getSession(false);

 

        if (session != null) {

            SessionInformation info = sessionRegistry.getSessionInformation(session.getId());

 

            if (info != null) {

                 if (info.isExpired()) {

                    // Expired - abort processing

                    session.invalidate();

 

                    String targetUrl = httpRequest.getContextPath() + expiredUrl;

//Session 已经过期,转向一个 targetUrl ,比如登陆页面

                    httpResponse.sendRedirect(httpResponse.encodeRedirectURL(targetUrl));

 

                    return;

                } else {

                    // Non-expired - update last request date/time

                    info.refreshLastRequest();

                }

            }

        }

 

        chain.doFilter(request, response);

    }

各位看官可能要问为什么要调用 SessionInformation.isExpired() 来检测通过 HttpSession 的 id 来获取 SessionInformation 是否过期呢,过期 ( 超时 ) 的SessionInformation 不是由 SessionRegistryImpl 清除掉了吗?

 

如果你能跟我一样发现这个问题,说明看得比较仔细和投入 :)

下面来介绍一下 ConcurrentSessionController 接口,因为 SpringSide 里面用了这个实现,所以一定要介绍下:)。

ConcurrentSessionController 接口有两个方法: public void checkAuthenticationAllowed(Authentication request) 和 public void registerSuccessfulAuthentication(Authentication authentication) 。前一个方法检查当前用户请求的认证 ( 这里是姑且把 Authentication 翻译成认证 ) 是否是系统允许的,如果没有经过允许则抛出 AuthenticationException 异常给 Acegi 处理;后一个方法把认证赋予当前用户。

 

Acegi 提供 ConcurrentSessionController 接口的一个增强实现 ConcurrentSessionControllerImpl ,提供更多的控制策略,比如配置是否允许用户重复登陆,最多允许多少个 HttpSession 。

下面看下 ConcurrentSessionController 的接口实现 ConcurrentSessionControllerImpl ,不过是我们只看其中一部分, Aecgi 是怎么让 SessionInformation 过期的

 

 

// 允许 HttpSession 超过设定值   

  protected void allowableSessionsExceeded(String sessionId, SessionInformation[] sessions, int allowableSessions,

        SessionRegistry registry) {

        if (exceptionIfMaximumExceeded || (sessions == null)) {

            throw new ConcurrentLoginException(messages.getMessage("ConcurrentSessionControllerImpl.exceededAllowed",

                    new Object[] {new Integer(allowableSessions)}, "Maximum sessions of {0} for this principal exceeded"));

        }

// 检查 SessionRegistryImpl 中最后更新的 SessionInformation

        SessionInformation leastRecentlyUsed = null;

 

        for (int i = 0; i < sessions.length; i++) {

            if ((leastRecentlyUsed == null) || sessions[i].getLastRequest().before(leastRecentlyUsed.getLastRequest())) {

                 leastRecentlyUsed = sessions[i];

            }

        }

// 找到一个倒霉蛋,对不起,我要把你的 SessionInformation 设置为过期,这样 ConcurrentSessionFilter 在处理到你的 SessionInformation 时会让你重新登陆系统

        leastRecentlyUsed.expireNow();

    }

 

 

分析到这里,不禁感叹一句, Acegi 提供了多么贴心的功能,感激得快流泪了。

 

写了这么多,你也看了这么多,快要骂我了,写了这么多,就说了 Acegi 怎么处理 HttpSession 超时,怎么处理每个 HttpSession 中的用户信息,怎么管理 Session 用户,以及那个倒霉蛋,还有那个倒霉蛋转到登陆页面再次输入用户名和密码的。

那用户访问受限资源时, Acegi 又是怎么做的呢。好,各位稍休息一会,我也抽根烟调整下思路。

 

 

 

好,第二部分开始了。 Acegi 怎么保护受限资源的。这里稍微点一下,资源可以分为多种,比如某个类的方法啊, URL 啊,只要写相应的拦截器去处理就 OK 了。拦截器检查到该用户没有权限访问资源,很简单,抛出异常 ( 至于针对方法和 URL 的拦截器是怎么工作的,这部分我们先不讲 )

好戏正式开始 ---Acegi 怎么处理这个异常的。你可以已经想到了,使用 Filter 。对,是 Filter ! Acegi 定义个 Filter ,用来处理这些异常,这个 Filter 是org.acegisecurity.ui.ExceptionTranslationFilter , ExceptionTranslationFilter

 

 

        try {

            chain.doFilter(request, response);

            if (logger.isDebugEnabled()) {

                logger.debug("Chain processed normally");

  

猜你喜欢

转载自sinoalex.iteye.com/blog/1228306