转自:http://www.zblog.us/java/shiro_session_manager.html
shiro提供了一个完整的企业级会话管理解决方案,不再依赖web容器。可以在web和非web环境下使用。
shiro的session特性
- 基于POJO/J2SE:shiro中session相关的类都是基于接口实现的简单的java对象(POJO),兼容所有java对象的配置方式,扩展也更方便,完全可以定制自己的会话管理功能 。
- 简单灵活的会话存储/持久化:因为shiro中的session对象是基于简单的java对象的,所以你可以将session存储在任何地方,例如,文件,各种数据库,内存中等。
- 容器无关的集群功能:shiro中的session可以很容易的集成第三方的缓存产品完成集群的功能。例如,Ehcache + Terracotta, Coherence, GigaSpaces等。你可以很容易的实现会话集群而无需关注底层的容器实现。
- 异构客户端的访问:可以实现web中的session和非web项目中的session共享。
- 会话事件监听:提供对对session整个生命周期的监听。
- 保存主机地址:在会话开始session会存用户的ip地址和主机名,以此可以判断用户的位置。
- 会话失效/过期的支持:用户长时间处于不活跃状态可以使会话过期,调用touch()方法,可以主动更新最后访问时间,让会话处于活跃状态。
- 透明的Web支持:shiro全面支持Servlet 2.5中的session规范。这意味着你可以将你现有的web程序改为shiro会话,而无需修改代码。
- 单点登录的支持:shiro session基于普通java对象,使得它更容易存储和共享,可以实现跨应用程序共享。可以根据共享的会话,来保证认证状态到另一个程序。从而实现单点登录。
使用会话
可以从当前的Subject中获取会话。
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute( "someKey", someValue);
获取session的subject.getSession()方法等价于currentUser.getSubject(true)。
- Suject.getSession(boolean create) 与web中的 HttpServletRequest.getSession(boolean create) 类似。
- 如果Subject已经拥有一个session,则方法中的boolean类型参数将会忽略,并直接返回已经存在的session。
- 如果Subject里没有拥有session,如果参数为true,则创建一个新的session并返回。
- 如果Subject里没有拥有session,如果参数为false,则不会创建新的session,并返回null。
返回值 | 方法名 | 描述 |
Object | getAttribute(Object key) | 根据key标识返回绑定到session的对象 |
Collection<Object> | getAttributeKeys() | 获取在session中存储的所有的key |
String | getHost() | 获取当前主机ip地址,如果未知,返回null |
Serializable | getId() | 获取session的唯一id |
Date | getLastAccessTime() | 获取最后的访问时间 |
Date | getStartTimestamp() | 获取session的启动时间 |
long | getTimeout() | 获取session失效时间,单位毫秒 |
void | setTimeout(long maxIdleTimeInMillis) | 设置session的失效时间 |
Object | removeAttribute(Object key) | 通过key移除session中绑定的对象 |
void | setAttribute(Object key, Object value) | 设置session会话属性 |
void | stop() | 销毁会话 |
void | touch() | 更新会话最后访问时间 |
会话管理器
SessionManager管理所有Subject的session包括创建、维护、删除、失效、验证等工作。SessionManager是顶层组件,由SecurityManager管理。
SecurityManager的实现类DefaultSecurityManager及DefaultWebSecurityManager继承了SessionsSecurityManager。
SessionsSecurityManager可以把相应的会话管理委托给SessionManager。
例如SessionsSecurityManager中的代码
public Session start(SessionContext context) throws AuthorizationException {
//委托给SessionManager
return this.sessionManager.start(context);
}
public Session getSession(SessionKey key) throws SessionException {
//委托给SessionManager
return this.sessionManager.getSession(key);
}
SecurityManager相关的类。
非web相关的
web相关的
shiro提供了三个SessionManager的实现
- DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于非web环境。
- ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话。
- DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager自己维护着会话,容器无关。
像SecurityManager其它组件一样,可以使用getter/setter方法获取和设置组件,同时也支持ini配置。
[main]
...
sessionManager = com.foo.my.SessionManagerImplementation
securityManager.sessionManager = $sessionManager
会话失效时间
全局会话失效时间
[main]
...
# 3,600,000 milliseconds = 1 hour
securityManager.sessionManager.globalSessionTimeout = 3600000
也可以为每个session单独设置会话失效时间
调用session的
setTimeout(long maxIdleTimeInMillis) ,参数为毫秒
对于
ServletContainerSessionManager,由于依赖于具体容器,所有失效时间要在容器里设置。
DefaultWebSessionManager容器无关的SessionMannager
ini配置
sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionIdCookie.name=sid
#sessionIdCookie.domain=zblog.us
#sessionIdCookie.path=
sessionIdCookie.maxAge=1800
sessionIdCookie.httpOnly=true
sessionManager.sessionIdCookie=$sessionIdCookie
sessionManager.sessionIdCookieEnabled=true
securityManager.sessionManager=$sessionManager
sessionIdCookie是sessionManager创建会话Cookie的模板。
- sessionIdCookie.name:设置Cookie名字,默认为JSESSIONID;
- sessionIdCookie.domain:设置Cookie的域名,默认空,即当前访问的域名;
- sessionIdCookie.path:设置Cookie的路径,默认空,即存储在域名根下;
- sessionIdCookie.maxAge:设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie;
- sessionIdCookie.httpOnly:如果设置为true,更安全, 防止 XSS 攻击
- sessionManager.sessionIdCookieEnabled:是否启用/禁用Session Id Cookie,默认是启用的;如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。
会话监听
ini配置
[main]
...
aSessionListener = com.foo.my.SessionListener
anotherSessionListener = com.foo.my.OtherSessionListener
securityManager.sessionManager.sessionListeners = $aSessionListener, $anotherSessionListener
要写自己的监听器,需要实现 SessionListener 接口
public class MySessionListener implements SessionListener {
@Override
public void onStart(Session session) {
//会话创建时触发
System.out.println("会话创建:" + session.getId());
}
@Override
public void onExpiration(Session session) {
//会话过期时触发
System.out.println("会话过期:" + session.getId());
}
@Override
public void onStop(Session session) {
//退出/会话过期时触发
System.out.println("会话停止:" + session.getId());
}
}
注:session监听是对所有的session监听,而不是针对某个特殊的session
会话存储/持久化
Session可以存储在内存或者各种数据库中。
SessionManager
委托SeesionDao对session进行增删改查。
你可以为SessionManager配置SessionDao
[main]
...
sessionDAO = com.foo.my.SessionDAO
securityManager.sessionManager.sessionDAO = $sessionDAO
如果使用的是
ServletContainerSessionManager,由于它是容器相关的,session也是有对应的容器管理的,无法使用SessionDao。
对于web项目可以使用
DefaultWebSessionManager。
[main]
...
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManagers
# Configure a SessionDAO and then set it:
securityManager.sessionManager.sessionDAO = $sessionDAO
EHCache SessionDAO
EHCache SessionDAO存储Session到内存,如果内存不够用的话,会保存到磁盘。Ehcache配合TerraCotta可以实现容器无关的分布式集群。
启用ehcache支持,在pom.xml中添加
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
为shiro所有用到缓存的地方,都配置成ehcache,自然SessionDao,也会被配置为EHCache来保存session。
ini配置
sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
#shiro默认的activeSessionsCacheName为shiro-activeSessionCache,
#如需重命名,可以这样设置
#sessionDAO.activeSessionsCacheName=shiro-activeSessionCache
sessionManager.sessionDAO=$sessionDAO
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
#shiro提供了默认的配置文件,如需自定义可以这样设置
#cacheManager.cacheManagerConfigFile=classpath:ehcache.xml
securityManager.cacheManager = $cacheManager
说明:
- sessionDAO.activeSessionsCacheName:设置Session缓存名字,默认就是shiro-activeSessionCache。
- cacheManager:缓存管理器,用于管理缓存的,这里使用Ehcache实现。
- cacheManager.cacheManagerConfigFile:设置ehcache缓存的配置文件,默认文件位置在shiro-ehcache包中。
- securityManager.cacheManager:设置SecurityManager的cacheManager,会自动设置实现了CacheManagerAware接口的相应对象,如SessionDAO的cacheManager。
EHCache的配置,默认在shiro-ehcache包中
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
overflowToDisk="true"
eternal="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
其中,name属性必须与上边ini配置中activeSessionsCacheNamename一致。
如果要自己配置的话有两点很重要。
- overflowToDisk=”true” :设置为true,表示如果内存不够用了,会将会话保存到硬盘。
- eternal=”true” :设置为true,表示会话对象的缓存不会被自动设置为过期或删除。shiro的session检查机制是基于调度器定时检测的,如果自动删除或者设置过期的话,shiro是无法知道session是否过期的,这样就会出现问题,所以要设置为true。
Session ID生成器
在每次创建session时,SessionDAO都会使用 SessionIdGenerator生成一个新的session ID, SessionIdGenerator默认实现是JavaUuidSessionIdGenerator,也就是生成UUID。
可以自定义自己的
SessionIdGenerator。
ini配置如下
[main]
...
sessionIdGenerator = com.my.session.SessionIdGenerator
securityManager.sessionManager.sessionDAO.sessionIdGenerator = $sessionIdGenerator
SessionDao的相关类
SessionDao接口定义了以下方法。
//如DefaultSessionManager在创建完session后会调用该方法;
//如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;
//返回会话ID;主要此处返回的ID.equals(session.getId());
Serializable create(Session session);
//根据会话ID获取会话
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
void update(Session session) throws UnknownSessionException;
//删除会话;当会话过期/会话停止(如用户退出时)会调用
void delete(Session session);
//获取当前所有活跃用户
Collection<Session> getActiveSessions();
- AbstractSessionDAO提供了SessionDAO的一些实现,例如生成会话id,创建会话。
- CachingSessionDAO提供了会话缓存的管理功能,需要为其设置CacheManager。
- MemorySessionDAO基于内存的,会话持久化实现。
- EnterpriseCacheSessionDAO继承MemorySessionDAO,并为其提供了一个MapCache作为简单的缓存管理器。在生产环境中如果直接使用EnterpriseCacheSessionDAO,推荐为其设置CacheManager,例如基于EHCache的EhCacheManager ,因为MapCache容易出现内存溢出,因为它无法持久化数据到硬盘。
自定义SessionDao继承
CachingSessionDAO即可,例如实现把会话保存到数据库,同时要为SessionDao设置
CacheManager,这样在获取session的时候会先从缓存获取,获取不到的时候才会查询数据库。
会话验证
Session必须通过验证才可以将无效过过期的session删除,出于性能的考虑,只有在获取会话的时候去验证会话是否过期。如果用户不主动退出,是无法知道session是否失效或过期的。如果不定期清理,session会越来越多。因此需要定期清理,shiro提供了会话验证调度器 SessionValidationScheduler来定期完成清除session的工作。
默认的调度器
默认的 SessionValidationScheduler调度器实现是 ExecutorServiceSessionValidationScheduler (基于JDKScheduledExecutorService实现的)。默认的调度周期是1小时,也就是没小时都会执行一次session验证,并清除过期或无效的session。
ini配置
[main]
...
sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
# 默认是3,600,000 毫秒 = 1 小时:
sessionValidationScheduler.interval = 3600000
securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
关闭调度器(默认是开启)
[main]
...
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
关闭无效session的删除(默认是开启)
main]
...
securityManager.sessionManager.deleteInvalidSessions = false
在web应用中,如果是在获取会话时验证了会话已过期,将抛出InvalidSessionException;因此需要捕获这个异常并跳转到相应的页面告诉用户会话已过期,让其重新登录,如可以在web.xml配置相应的错误页面:
<error-page>
<exception-type>org.apache.shiro.session.InvalidSessionException</exception-type>
<location>/invalidSession.jsp</location>
</error-page>
对于shiro实现集群功能,后续文章会介绍。