目录
1. 会话
1.1 Session 接口
1.2 SessionManager 会话管理
1.3 SessionListener 会话监听
2. 会话持久化
2.1 SessionDAO接口
2.2 AbstractSessionDAO类
2.3 CachingSessionDAO 类
2.4 EnterpriseCacheSessionDAO 类
2.5 MemorySessionDAO 类
3. 会话过期验证
3.1 SessionValidationScheduler接口
3.2 ExecutorServiceSessionValidationScheduler 类
当用户访问我们的应用时,为了方便,将用户的信息(不止是用户信息)保存起来,下次用户访问的时候,应用就能识别出用户。shiro也提供了会话机制,可以代替Web的会话机制,Session接口是会话的核心。
public interface Session {
Serializable getId();
Date getStartTimestamp();
Date getLastAccessTime();
long getTimeout() throws InvalidSessionException;
void setTimeout(long var1) throws InvalidSessionException;
String getHost();
void touch() throws InvalidSessionException;
void stop() throws InvalidSessionException;
Collection
可以看出会话可以保存的信息有:会话id,会话创建时间,最近一次访问会话的时间,会话超时时间,用户主机,键值对(用来存储一些额外自定义的数据的)。
Session接口的实现类有多个:SimpleSession(简单地实现了Session接口)
SimpleSession 简单的会话实现类,我们一般都用这个,当然还有其他的会话类。
public class SimpleSession implements ValidatingSession, Serializable {
protected static final long MILLIS_PER_SECOND = 1000L; //这是一秒的常量
protected static final long MILLIS_PER_MINUTE = 60000L; //一分钟的常量
protected static final long MILLIS_PER_HOUR = 3600000L; //一小时的常量
static int bitIndexCounter = 0;
private static final int ID_BIT_MASK;
private static final int START_TIMESTAMP_BIT_MASK;
private static final int STOP_TIMESTAMP_BIT_MASK;
private static final int LAST_ACCESS_TIME_BIT_MASK;
private static final int TIMEOUT_BIT_MASK;
private static final int EXPIRED_BIT_MASK;
private static final int HOST_BIT_MASK;
private static final int ATTRIBUTES_BIT_MASK;
private transient Serializable id; //会话ID,就是Sessionid
private transient Date startTimestamp; //会话开始时间
private transient Date stopTimestamp; //会话结束时间
private transient Date lastAccessTime; //上一次会话访问的时间
private transient long timeout; //会话过期时间
private transient boolean expired; //会话是否过期了
private transient String host; //用户主机
private transient Map
SessionManager 接口,提供两个方法,start 创建并启动一个会话,根据SessionKey获取会话,Key其实就是会话id,id就是能够唯一标识用户的一个数据,并非一定要是数字。
public interface SessionManager {
Session start(SessionContext var1);
Session getSession(SessionKey var1) throws SessionException;
}
一个接口,一个实现类,比较简单。
SessionListener 接口源码:
public interface SessionListener {
void onStart(Session var1); //创建一个Session的时候执行
void onStop(Session var1); //会话退出或者过期执行
void onExpiration(Session var1); //会话过期时执行
}
SessionListenerAdapter 类源码:
public class SessionListenerAdapter implements SessionListener {
public SessionListenerAdapter() {}
public void onStart(Session session) {}
public void onStop(Session session) {}
public void onExpiration(Session session) {}
}
两种方式:实现接口SessionListener,继承类SessionListenerAdapter。
1. 实现接口SessionListener,就要实现三个方法,没得选。 2. 继承类SessionListenerAdapter,就可以选择性得去重载一两个方法。
会话一般是放在内存中的,但是对于一个分布式集群来讲,需要共享会话,此时就需要将会话持久化。
下图中的实现类,并不是进行持久化,而是将会话存储在缓存中,或者内存中。
所以,要想将会话存到数据库里,就要自己定义一个SessionDAO。
增、删、查、改、获取活跃会话。
public interface SessionDAO {
Serializable create(Session var1);
Session readSession(Serializable var1) throws UnknownSessionException;
void update(Session var1) throws UnknownSessionException;
void delete(Session var1);
Collection getActiveSessions();
}
SessionIdGenerator是ID生成器(调用UUID.randomUUID().toString()生成);
虽然实现了create()方法,但是并未实现doCreate()方法,等于还是不能创建Session。
verifySessionId(Serializable sessionId) 方法是判断会话是否存在。
assignSessionId(Session session, Serializable sessionId) 方法是设置session的id为sessionId。
readSession(Serializable sessionId) 方法是根据sessionId 读取 Session。
public abstract class AbstractSessionDAO implements SessionDAO {
private SessionIdGenerator sessionIdGenerator = new JavaUuidSessionIdGenerator();
public AbstractSessionDAO() {}
public SessionIdGenerator getSessionIdGenerator() {
return this.sessionIdGenerator;
}
public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
this.sessionIdGenerator = sessionIdGenerator;
}
protected Serializable generateSessionId(Session session) {
if (this.sessionIdGenerator == null) {
String msg = "sessionIdGenerator attribute has not been configured.";
throw new IllegalStateException(msg);
} else {
return this.sessionIdGenerator.generateId(session);
}
}
public Serializable create(Session session) {
Serializable sessionId = this.doCreate(session);
this.verifySessionId(sessionId);
return sessionId;
}
private void verifySessionId(Serializable sessionId) {
if (sessionId == null) {
String msg = "sessionId returned from doCreate implementation is null. Please verify the implementation.";
throw new IllegalStateException(msg);
}
}
protected void assignSessionId(Session session, Serializable sessionId) {
((SimpleSession)session).setId(sessionId);
}
protected abstract Serializable doCreate(Session var1);
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = this.doReadSession(sessionId);
if (s == null) {
throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
} else {
return s;
}
}
protected abstract Session doReadSession(Serializable var1);
}
这个类一看名字,就大概知道是用缓存来存储Session。
ACTIVE_SESSION_CACHE_NAME是用来存储Session的缓存块的名字,不可修改。
CacheManager 是缓存管理器,一个接口,可以猜想这个管理就是提供很多可以操作缓存的方法。
Cache
activeSessionsCacheName 这个字符串 存储的也是缓存块的名字,但是可以修改。
还有一些方法,就不做详细介绍了,一看方法的名字,就能知道是干什么的,基本上就是:创建缓存、对缓存中的会话进行增、删、查、改。 注意:实际上并没有具体实现对会话的修改和删除,这两个留着给开发者自定义。
public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
private CacheManager cacheManager;
private Cache activeSessions;
private String activeSessionsCacheName = "shiro-activeSessionCache";
public CachingSessionDAO() {
}
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public CacheManager getCacheManager() {
return this.cacheManager;
}
public String getActiveSessionsCacheName() {
return this.activeSessionsCacheName;
}
public void setActiveSessionsCacheName(String activeSessionsCacheName) {
this.activeSessionsCacheName = activeSessionsCacheName;
}
public Cache getActiveSessionsCache() {
return this.activeSessions;
}
public void setActiveSessionsCache(Cache cache) {
this.activeSessions = cache;
}
private Cache getActiveSessionsCacheLazy() {
if (this.activeSessions == null) {
this.activeSessions = this.createActiveSessionsCache();
}
return this.activeSessions;
}
protected Cache createActiveSessionsCache() {
Cache cache = null;
CacheManager mgr = this.getCacheManager();
if (mgr != null) {
String name = this.getActiveSessionsCacheName();
cache = mgr.getCache(name);
}
return cache;
}
public Serializable create(Session session) {
Serializable sessionId = super.create(session);
this.cache(session, sessionId);
return sessionId;
}
protected Session getCachedSession(Serializable sessionId) {
Session cached = null;
if (sessionId != null) {
Cache cache = this.getActiveSessionsCacheLazy();
if (cache != null) {
cached = this.getCachedSession(sessionId, cache);
}
}
return cached;
}
protected Session getCachedSession(Serializable sessionId, Cache cache) {
return (Session)cache.get(sessionId);
}
protected void cache(Session session, Serializable sessionId) {
if (session != null && sessionId != null) {
Cache cache = this.getActiveSessionsCacheLazy();
if (cache != null) {
this.cache(session, sessionId, cache);
}
}
}
protected void cache(Session session, Serializable sessionId, Cache cache) {
cache.put(sessionId, session);
}
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = this.getCachedSession(sessionId);
if (s == null) {
s = super.readSession(sessionId);
}
return s;
}
public void update(Session session) throws UnknownSessionException {
this.doUpdate(session);
if (session instanceof ValidatingSession) {
if (((ValidatingSession)session).isValid()) {
this.cache(session, session.getId());
} else {
this.uncache(session);
}
} else {
this.cache(session, session.getId());
}
}
protected abstract void doUpdate(Session var1);
public void delete(Session session) {
this.uncache(session);
this.doDelete(session);
}
protected abstract void doDelete(Session var1);
protected void uncache(Session session) {
if (session != null) {
Serializable id = session.getId();
if (id != null) {
Cache cache = this.getActiveSessionsCacheLazy();
if (cache != null) {
cache.remove(id);
}
}
}
}
public Collection getActiveSessions() {
Cache cache = this.getActiveSessionsCacheLazy();
return (Collection)(cache != null ? cache.values() : Collections.emptySet());
}
}
在看看这个类,事实证明,确实是将会话的修改和删除留给我们开发者去做,这个类实现了CachingSessionDAO里的抽象方法,所以这个类才能被实例化。
public class EnterpriseCacheSessionDAO extends CachingSessionDAO {
public EnterpriseCacheSessionDAO() {
this.setCacheManager(new AbstractCacheManager() {
protected Cache createCache(String name) throws CacheException {
return new MapCache(name, new ConcurrentHashMap());
}
});
}
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
return sessionId;
}
protected Session doReadSession(Serializable sessionId) {
return null;
}
protected void doUpdate(Session session) {
}
protected void doDelete(Session session) {
}
}
这个类是用一个ConcurrentHashMap<>来存储Session的,这个ConcurrentHashMap<> 和 HashMap 用法一样,功能一样,只是ConcurrentHashMap<> 是多线程安全的,而HashMap<>是线程不安全的。
方法就不一一详细介绍了,基本也就是创建、删除、修改、查询等等。
public class MemorySessionDAO extends AbstractSessionDAO {
private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
private ConcurrentMap sessions = new ConcurrentHashMap();
public MemorySessionDAO() {
}
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.storeSession(sessionId, session);
return sessionId;
}
protected Session storeSession(Serializable id, Session session) {
if (id == null) {
throw new NullPointerException("id argument cannot be null.");
} else {
return (Session)this.sessions.putIfAbsent(id, session);
}
}
protected Session doReadSession(Serializable sessionId) {
return (Session)this.sessions.get(sessionId);
}
public void update(Session session) throws UnknownSessionException {
this.storeSession(session.getId(), session);
}
public void delete(Session session) {
if (session == null) {
throw new NullPointerException("session argument cannot be null.");
} else {
Serializable id = session.getId();
if (id != null) {
this.sessions.remove(id);
}
}
}
public Collection getActiveSessions() {
Collection values = this.sessions.values();
return (Collection)(CollectionUtils.isEmpty(values) ? Collections.emptySet() : Collections.unmodifiableCollection(values));
}
}
Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler来做这件事情。
Scheduler 单词的意思就是 行程,计划,所以这个接口的意思就是:周期性地、按计划性地对会话进行验证。
public interface SessionValidationScheduler {
boolean isEnabled(); //返回当前验证器是否可用,true为可用,false为不可用
void enableSessionValidation(); //将验证器设置为可用
void disableSessionValidation(); //将验证器设置为不可用
}
ValidatingSessionManager 是接口,验证会话管理器,可以调用方法validateSessions() 进行验证。
ScheduledExecutorService 是接口,可以对认证线程进行操作管理。
interval 是验证的间隔时间。
threadNamePrefix 是验证线程的线程名。
启动一个线程去验证session。
实现了 disableSessionValidation() 方法 和 eableSessionValidation() 方法。
public class ExecutorServiceSessionValidationScheduler implements SessionValidationScheduler, Runnable {
private static final Logger log = LoggerFactory.getLogger(ExecutorServiceSessionValidationScheduler.class);
ValidatingSessionManager sessionManager;
private ScheduledExecutorService service;
private long interval = 3600000L;
private boolean enabled = false;
private String threadNamePrefix = "SessionValidationThread-";
public ExecutorServiceSessionValidationScheduler() { }
public ExecutorServiceSessionValidationScheduler(ValidatingSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public ValidatingSessionManager getSessionManager() {
return this.sessionManager;
}
public void setSessionManager(ValidatingSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public long getInterval() {
return this.interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
public boolean isEnabled() {
return this.enabled;
}
public void setThreadNamePrefix(String threadNamePrefix) {
this.threadNamePrefix = threadNamePrefix;
}
public String getThreadNamePrefix() {
return this.threadNamePrefix;
}
public void enableSessionValidation() {
if (this.interval > 0L) {
this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName(ExecutorServiceSessionValidationScheduler.this.threadNamePrefix + this.count.getAndIncrement());
return thread;
}
});
this.service.scheduleAtFixedRate(this, this.interval, this.interval, TimeUnit.MILLISECONDS);
}
this.enabled = true;
}
public void run() {
if (log.isDebugEnabled()) {
log.debug("Executing session validation...");
}
long startTime = System.currentTimeMillis();
this.sessionManager.validateSessions();
long stopTime = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
}
}
public void disableSessionValidation() {
if (this.service != null) {
this.service.shutdownNow();
}
this.enabled = false;
}
}