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

本文详细介绍了 Acegi 在 Web 应用中如何利用 HttpSessionEvent 监听机制进行会话管理和异常处理。通过分析 Acegi 的核心组件,如 HttpSessionEventPublisher 和 SessionRegistryImpl,解释了 Acegi 如何监控 HttpSession 生命周期并确保系统的安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 来获取一个 SpringApplicationContext 。后者是在 HttpSession 创建和超时时发布利用 SpringApplicationContext. publishEvent() 方法发布事件的,注意并不时发布的 HttpSesionEvent ,而是有 HttpSession 作为构造参数,创建了 HttpSessionCreatedEventHttpSessionDestroyedEvent ,这两个 Event 都是 SpringApplicationEvent 的子类,这样 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 需要存储客户端用户的 SessionInformationSessionInformation 里面又有什么, 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 没有处理 HttpSessionCreatedEventAcegi 也没有提供其他的 Spring ApplicationListener 来处理这个 HttpSessionCreatedEvent

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

 

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

只是如此而已。

 

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

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

 

    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() 来检测通过 HttpSessionid 来获取 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 。对,是 FilterAcegi 定义个 Filter ,用来处理这些异常,这个 Filterorg.acegisecurity.ui.ExceptionTranslationFilterExceptionTranslationFilter

 

 

        try {

            chain.doFilter(request, response);

            if (logger.isDebugEnabled()) {

                logger.debug("Chain processed normally");

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值