shiro的会话管理
1.三个会话管理器(SessionManager)实现
1.1DefaultSessionManager
DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于 JavaSE 环境;
1.2ServletContainerSessionManager
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默认实现,用于 Web 环境,其直接使用 Servlet 容器的会话;
1.3DefaultWebSessionManager
DefaultWebSessionManager:用于 Web 环境的实现,可以替代 ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。
se环境下的 ini 配置 (shiro.ini):
[main]
sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
securityManager.sessionManager=$sessionManager;
Web 环境下的 ini 配置 (shiro-web.ini):
[main]
sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager #DefaultWebSessionManager
securityManager.sessionManager=$sessionManager
2.会话监听器
2.1SessionListener 和 SessionListenerAdapter
1.SessionListener实现方法并重写三个接口
public class MySessionListener1 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());
}
}
2.SessionListenerAdapter (区别于SessionListen :可以只使用其中的一个方法)
public class MySessionListener2 extends SessionListenerAdapter {
@Override
public void onStart(Session session) {
System.out.println("会话创建:" + session.getId());
}
}
shiro.ini文件配置
[main]
sessionListener1=com.github.shiro.web.listener.MySessionListener1
sessionListener2=com.github.shiro.web.listener.MySessionListener2
sessionManager.sessionListeners=$sessionListener1,$sessionListener2
3.会话存储
3.1SessionDao常用实现类
AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话 ID 等;
CachingSessionDAO 提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;
MemorySessionDAO 直接在内存中进行会话维护;EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。
3.2SessionDao的配置
Shiro 提供了使用 Ehcache 进行会话存储,Ehcache 可以配合 TerraCotta 实现容器无关的分布式集群。
org.apache.shiro
shiro-ehcache
1.2.2
配置 shiro-web.ini 文件
sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionDAO. activeSessionsCacheName=shiro-activeSessionCache
sessionManager.sessionDAO=$sessionDAO
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:ehcache.xml
securityManager.cacheManager = $cacheManager;
sessionDAO. activeSessionsCacheName:设置 Session 缓存名字,默认就是 shiro-activeSessionCache;
cacheManager:缓存管理器,用于管理缓存的,此处使用 Ehcache 实现;
cacheManager.cacheManagerConfigFile:设置 ehcache 缓存的配置文件;
securityManager.cacheManager:设置 SecurityManager 的 cacheManager,会自动设置实现了 CacheManagerAware 接口的相应对象,如 SessionDAO 的 cacheManager;
配置 ehcache.xml:
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
statistics="true"/>
Cache 的名字为 shiro-activeSessionCache,即设置的 sessionDAO 的 activeSessionsCacheName 属性值。
另外可以通过如下 ini 配置设置会话 ID 生成器:
sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator
sessionDAO.sessionIdGenerator=$sessionIdGenerator;
3.3自定义实现
自定义实现 SessionDAO,继承 CachingSessionDAO 即可:
public class MySessionDAO extends CachingSessionDAO {
private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
String sql = "insert into sessions(id, session) values(?,?)";
jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));
return session.getId();
}
protected void doUpdate(Session session) {
if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
return; //如果会话过期/停止 没必要再更新了
}
String sql = "update sessions set session=? where id=?";
jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());
}
protected void doDelete(Session session) {
String sql = "delete from sessions where id=?";
jdbcTemplate.update(sql, session.getId());
}
protected Session doReadSession(Serializable sessionId) {
String sql = "select session from sessions where id=?";
List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);
if(sessionStrList.size() == 0) return null;
return SerializableUtils.deserialize(sessionStrList.get(0));
}
};
doCreate/doUpdate/doDelete/doReadSession 分别代表创建 / 修改 / 删除 / 读取会话;此处通过把会话序列化后存储到数据库实现;接着在 shiro-web.ini 中配置:
sessionDAO=com.github.shiro.session.dao.MySessionDAO
其他设置和之前一样,因为继承了 CachingSessionDAO;所有在读取时会先查缓存中是否存在,如果找不到才到数据库中查找。
4.验证会话是否过期
Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;
但是如在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器
4.1SessionValidationScheduler 会话验证调度器
sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
sessionValidationScheduler.interval = 3600000
sessionValidationScheduler.sessionManager=$sessionManager
sessionManager.globalSessionTimeout=1800000
sessionManager.sessionValidationSchedulerEnabled=true
sessionManager.sessionValidationScheduler=$sessionValidationScheduler;
sessionValidationScheduler:会话验证调度器,sessionManager 默认就是使用 ExecutorServiceSessionValidationScheduler,其使用 JDK 的 ScheduledExecutorService 进行定期调度并验证会话是否过期;
sessionValidationScheduler.interval:设置调度时间间隔,单位毫秒,默认就是 1 小时;
sessionValidationScheduler.sessionManager:设置会话验证调度器进行会话验证时的会话管理器;
sessionManager.globalSessionTimeout:设置全局会话超时时间,默认 30 分钟,即如果 30 分钟内没有访问会话将过期;
sessionManager.sessionValidationSchedulerEnabled:是否开启会话验证器,默认是开启的;
sessionManager.sessionValidationScheduler:设置会话验证调度器,默认就是使用 ExecutorServiceSessionValidationScheduler。
4.2Quartz 会话验证调度器:
sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler
sessionValidationScheduler.sessionValidationInterval = 3600000
sessionValidationScheduler.sessionManager=$sessionManager
使用时需要导入 shiro-quartz 依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.2</version>
</dependency>
5.sessionFactory
sessionFactory 是创建会话的工厂,根据相应的 Subject 上下文信息来创建会话;默认提供了 SimpleSessionFactory 用来创建 SimpleSession 会话。
首先自定义一个 Session:
public class OnlineSession extends SimpleSession {
public static enum OnlineStatus {
on_line("在线"), hidden("隐身"), force_logout("强制退出");
private final String info;
private OnlineStatus(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
}
private String userAgent; //用户浏览器类型
private OnlineStatus status = OnlineStatus.on_line; //在线状态
private String systemHost; //用户登录时系统IP
//省略其他
};
OnlineSession 用于保存当前登录用户的在线状态,支持如离线等状态的控制。
接着自定义 SessionFactory:
public class OnlineSessionFactory implements SessionFactory {
@Override
public Session createSession(SessionContext initData) {
OnlineSession session = new OnlineSession();
if (initData != null && initData instanceof WebSessionContext) {
WebSessionContext sessionContext = (WebSessionContext) initData;
HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();
if (request != null) {
session.setHost(IpUtils.getIpAddr(request));
session.setUserAgent(request.getHeader("User-Agent"));
session.setSystemHost(request.getLocalAddr() + ":" + request.getLocalPort());
}
}
return session;
}
};
根据会话上下文创建相应的 OnlineSession。
最后在 shiro-web.ini 配置文件中配置:
sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory
sessionManager.sessionFactory=$sessionFactory
6.会话常用方法总结
1.获得session
Session session = subject.getSession();
2.获取当前会话的唯一标识。
session.getId();
3.获取当前 Subject 的主机地址,该地址是通过 HostAuthenticationToken.getHost() 提供的。
session.getHost();
4.获取 / 设置当前 Session 的过期时间;如果不设置默认是会话管理器的全局过期时间。
session.getTimeout();
session.setTimeout(毫秒);
5.获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用
session.getStartTimestamp();
session.getLastAccessTime();
6.更新最后访问时间。
session.touch();
7.更新会话最后访问时间及销毁会话
session.stop();
8.设置 / 获取 / 删除会话属性;在整个会话范围内都可以对这些属性进行操作。
session.setAttribute(“key”, “123”);
Assert.assertEquals(“123”, session.getAttribute(“key”));
session.removeAttribute(“key”);