您的位置:首页 > 其它

Shiro会话管理

2015-07-15 14:15 447 查看
转自: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);

[/code]

获取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。

返回值方法名描述
ObjectgetAttribute(Object key) 根据key标识返回绑定到session的对象
Collection<Object>getAttributeKeys() 获取在session中存储的所有的key
StringgetHost()获取当前主机ip地址,如果未知,返回null
SerializablegetId() 获取session的唯一id
DategetLastAccessTime() 获取最后的访问时间
DategetStartTimestamp() 获取session的启动时间
longgetTimeout() 获取session失效时间,单位毫秒
voidsetTimeout(long maxIdleTimeInMillis) 设置session的失效时间
ObjectremoveAttribute(Object key) 通过key移除session中绑定的对象
voidsetAttribute(Object key, Object value) 设置session会话属性
voidstop() 销毁会话
voidtouch() 更新会话最后访问时间


会话管理器

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);

}

[/code]


SecurityManager相关的类。

非web相关的





shiro session

web相关的





web session

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

[/code]

会话失效时间

全局会话失效时间

[main]

...

# 3,600,000 milliseconds = 1 hour

securityManager.sessionManager.globalSessionTimeout = 3600000

[/code]

也可以为每个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

[/code]

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

[/code]

要写自己的监听器,需要实现 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());

}

}

[/code]

注:session监听是对所有的session监听,而不是针对某个特殊的session


会话存储/持久化

Session可以存储在内存或者各种数据库中。SessionManager委托SeesionDao对session进行增删改查。

你可以为SessionManager配置SessionDao

[main]

...

sessionDAO = com.foo.my.SessionDAO

securityManager.sessionManager.sessionDAO = $sessionDAO

[/code]

如果使用的是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

[/code]


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>

[/code]

为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

[/code]

说明:

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"/>

[/code]

其中,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

[/code]

SessionDao的相关类





session dao

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();

[/code]

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

[/code]

关闭调度器(默认是开启)

[main]

...

securityManager.sessionManager.sessionValidationSchedulerEnabled = false

[/code]

关闭无效session的删除(默认是开启)

main]

...

securityManager.sessionManager.deleteInvalidSessions = false

[/code]

在web应用中,如果是在获取会话时验证了会话已过期,将抛出InvalidSessionException;因此需要捕获这个异常并跳转到相应的页面告诉用户会话已过期,让其重新登录,如可以在web.xml配置相应的错误页面:

<error-page>

<exception-type>org.apache.shiro.session.InvalidSessionException</exception-type>

<location>/invalidSession.jsp</location>

</error-page>

[/code]

对于shiro实现集群功能,后续文章会介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: