我们知道web应用中,容器通过session来保持会话,并且存储一些会话数据。
而Shiro除了提供安全验证,还提供了一个完整的企业级Session会话解决方案,使得即使是非Web应用也可以进行会话的管理。
public interface Session {
//类似于HttpSession, 定义了一些标准的session属性与操作,
//包括Session唯一标识、会话超时时间、会话开始时间、最后访问时间、主机IP、停止会话、通过attribute绑定、删除、数据等
}
//Session管理器,负责创建、持久化和删除会话实例及其生命周期。
public interface SessionManager{
Session start(SessionContext context); //创建一个新的会话
//根据SessionKey获取会话,如果没有找到会话,返回null.
//如果找到一个无效会话(过期或停止),抛出SessionException
Session getSession(SessionKey key) throws SessionException;
}
public interface SessionDAO {
//将一个新的Session记录保存到EIS(关系型数据库、文件系统、持久缓存,等等)
Serializable create(Session session);
//从EIS中检索Session
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新Session信息
void update(Session session) throws UnknownSessionException;
//删除Session,如果EIS中没有该Session,什么都不做
void delete(Session session);
//返回全部存活的Session
Collection<Session> getActiveSessions();
}
Shiro通过以上三个接口来实现Session的会话管理。
我们知道Shiro中一切的操作都是通过SecurityManager来实现的。
实际上 SecurityManager 继承了 SessionManager ,实际操作也是通过SessionsSecurityManager来进行的。
查看 SessionsSecurityManager代码,可以看到 SecurityManager 是委托给一个可用的SessionManager来执行,默认使用DefaultSessionManager.
实际上Shiro只为SessionManager提供了 DefaultSessionManager 一个可用的实现类
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
private SessionManager sessionManager;
public SessionsSecurityManager() {
super();
this.sessionManager = new DefaultSessionManager();
applyCacheManagerToSessionManager();
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
afterSessionManagerSet();
}
public Session start(SessionContext context) throws AuthorizationException {
return this.sessionManager.start(context);
}
public Session getSession(SessionKey key) throws SessionException {
return this.sessionManager.getSession(key);
}
####Session的创建与存取
查看DefaultSessionManager代码可以看到Session的创建和获取( start(context) 与 getSession(key) 方法 )
都是通过SessionDAO来实现的。
SessionManager.start(SessionContext context)的实现:
public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware{
// SessionManager start(context) 的实现
@Override
public Session start(SessionContext context) {
//通过调用子类的实现来获取Session
Session session = createSession(context);
//设置会话的存活时间为全局存活时间,默认30分钟
applyGlobalSessionTimeout(session);
//事件通知,后面分析
onStart(session, context);
notifyStart(session);
//对Session进行封装,返回DelegatingSession对象。
return createExposedSession(session, context);
}
//一般抽象类都会实现一个接口的方法,但是提供一个钩子方法给子类实现
protected abstract Session createSession(SessionContext context) throws AuthorizationException;
//对Session进行封装,返回 DelegatingSession 对象。
//DelegatingSession是 Shiro提供给Session模块外部访问的Session实例,它的一切操作都委托给SessionManager处理。
protected Session createExposedSession(Session session, SessionContext context) {
return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
}
}
public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
implements ValidatingSessionManager, Destroyable{
@Override
protected Session createSession(SessionContext context) throws AuthorizationException {
enableSessionValidationIfNecessary(); //尝试开启会话调度验证,后面再单独看
return doCreateSession(context);
}
}
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
@Override
protected Session doCreateSession(SessionContext context)
throws AuthorizationException {
Session s = newSessionInstance(context);
if (log.isTraceEnabled()) {
log.trace("Creating session for host {}", s.getHost());
}
create(s);
return s;
}
//通过工厂模式创建新的Session实例,创建的是 SimpleSession.
//SessionFacotry只有SimpleSessionFactory一个实现
protected Session newSessionInstance(SessionContext context) {
return getSessionFactory().createSession(context);
}
//将新建的Session存储到EIS
protected void create(Session session) {
sessionDAO.create(session);
}
}
SessionManager.getSession(SessionKey key)的实现:
public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware{
//SessionManager getSession(key) 的实现
@Override
public Session getSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
//createExposedSession(session, key) 对Session进行封装,返回DelegatingSession对象。
return session != null ? createExposedSession(session, key) : null;
}
private Session lookupSession(SessionKey key) throws SessionException {
if (key == null) {
throw new NullPointerException("SessionKey argument cannot be null.");
}
return doGetSession(key);
}
//钩子方法,调用的是 DefaultSessionManager 的实现
protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;
}
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
public DefaultSessionManager() {
this.deleteInvalidSessions = true;
this.sessionFactory = new SimpleSessionFactory();
this.sessionDAO = new MemorySessionDAO(); //默认使用基于内存的SessionDAO
}
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
if (sessionId == null) {
log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a " +
"session could not be found.", sessionKey);
return null;
}
//最终从sessionDAO中读取Sesssion
Session s = retrieveSessionFromDataSource(sessionId);
if (s == null) {
//session ID was provided, meaning one is expected to be found, but we couldn't find one:
String msg = "Could not find session with ID [" + sessionId + "]";
throw new UnknownSessionException(msg);
}
return s;
}
//最终从sessionDAO中读取Sesssion
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
return sessionDAO.readSession(sessionId);
}
}
Shiro提供了默认的基于内存的 MemorySessionDAO, 它通过一个 ConcurrentMap 来存储Session.
对于希望Session持久化或者需要共享Session来实现集群的应用,可以继承 EnterpriseCacheSessionDAO 来实现。
####失效Session的回收
对于失效Session进行回收是非常必要的,否则大量的会话由于没有手动退出,一直处于孤立状态,会导致大量内存或其它存储空间占用。
Shiro通过 SessionValidationScheduler 来进行会话的回收。
//会话验证调度器
public interface SessionValidationScheduler {
//调度器是否开启
boolean isEnabled();
//开启会话验证任务
void enableSessionValidation();
//关闭会话验证任务
void disableSessionValidation();
}
我们直接查看Shiro的具体实现:
public class ExecutorServiceSessionValidationScheduler implements SessionValidationScheduler, Runnable {
private static final Logger log = LoggerFactory.getLogger(ExecutorServiceSessionValidationScheduler.class);
//通过sessionManager来进行会话的验证操作
ValidatingSessionManager sessionManager;
//进行回收调度的线程池
private ScheduledExecutorService service;
//调度任务进行会话验证的时间间隔,默认1小时
private long interval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
//会话验证是否开启
private boolean enabled = false;
//调度线程的线程名称前缀
private String threadNamePrefix = "SessionValidationThread-";
public ExecutorServiceSessionValidationScheduler() {
super();
}
public ExecutorServiceSessionValidationScheduler(ValidatingSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
//getter & setter
/**
* 创建一个线程的ScheduledExecutorService验证会话以固定的间隔,使此调度器。执行程序是一个守护线程,允许JVM关闭
*/
@Override
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(threadNamePrefix + count.getAndIncrement());
return thread;
}
});
//线程池在interval 毫秒后开始 以 interval 毫秒 为周期 执行 run(),
this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
}
this.enabled = true;
}
@Override
public void disableSessionValidation() {
//首先停止线程
if (this.service != null) {
this.service.shutdownNow();
}
this.enabled = false;
}
@Override
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.");
}
}
}
那么SessionManager如何进行会话的验证与回收?直接看代码:
//此接口扩展了SessionManager, 增加会话验证方法来验证所有打开的或活动的会话。
public interface ValidatingSessionManager extends SessionManager {
void validateSessions();
}
public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager implements ValidatingSessionManager, Destroyable{
@Override
public void validateSessions() {
int invalidCount = 0;
//获取当前活动的所有Session
Collection<Session> activeSessions = getActiveSessions();
if (activeSessions != null && !activeSessions.isEmpty()) {
for (Session s : activeSessions) {
try {
SessionKey key = new DefaultSessionKey(s.getId());
//验证Session是否有效
validate(s, key);
} catch (InvalidSessionException e) {
invalidCount++;
}
}
}
}
//获取全部会话,DefaultSessionManager通过 SessionDAO来获取
protected abstract Collection<Session> getActiveSessions();
//验证单个会话是否有效, 通过调用 ValidatingSession.validate()方法是否抛出异常来判断
protected void validate(Session session, SessionKey key) throws InvalidSessionException {
try {
doValidate(session);
} catch (ExpiredSessionException ese) {
//会话过期,最后通过 afterExpired 方法 调用 sessionDAO.delete
onExpiration(session, ese, key);
throw ese;
} catch (InvalidSessionException ise) {
//会话失效,最后通过 afterStopped 方法 调用 sessionDAO.delete
onInvalidation(session, ise, key);
throw ise;
}
}
//调用ValidatingSession.validate()进行验证,此方法在会话过期或停止时会抛出异常
protected void doValidate(Session session) throws InvalidSessionException {
if (session instanceof ValidatingSession) {
((ValidatingSession) session).validate();// 如果会话过期或失效,此方法抛出异常
} else {
String msg = "......";//异常信息省略
throw new IllegalStateException(msg);
}
}
}
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
@Override
protected Collection<Session> getActiveSessions() {
Collection<Session> active = sessionDAO.getActiveSessions();
return active != null ? active : Collections.<Session>emptySet();
}
}
定义了Session的回收机制,Shiro在创建和获取Session时会尝试开启回收机制(如果已经开启,则跳过):
public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager implements ValidatingSessionManager, Destroyable{
//此方法在调用 sessionManager.start(context)时调用
protected Session createSession(SessionContext context) throws AuthorizationException {
enableSessionValidationIfNecessary();
return doCreateSession(context);
}
//此方法在调用 sessionManager.getSession(key)时调用
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
enableSessionValidationIfNecessary();
log.trace("Attempting to retrieve session with key {}", key);
Session s = retrieveSession(key);
if (s != null) {
validate(s, key);
}
return s;
}
//如果必要,开启会话验证
private void enableSessionValidationIfNecessary() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
enableSessionValidation();
}
}
//开启会话验证调度
protected synchronized void enableSessionValidation() {
// 获取调度器,没有则新建
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if (scheduler == null) {
scheduler = createSessionValidationScheduler();
setSessionValidationScheduler(scheduler);
}
if (!scheduler.isEnabled()) {
if (log.isInfoEnabled()) {
log.info("Enabling session validation scheduler...");
}
//开启验证
scheduler.enableSessionValidation();
afterSessionValidationEnabled();
}
}
//当scheduler为null时, 创建一个ExecutorServiceSessionValidationScheduler
protected SessionValidationScheduler createSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler;
if (log.isDebugEnabled()) {
log.debug("No sessionValidationScheduler set. Attempting to create default instance.");
}
scheduler = new ExecutorServiceSessionValidationScheduler(this);
scheduler.setInterval(getSessionValidationInterval());
if (log.isTraceEnabled()) {
log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
}
return scheduler;
}
}
综上, SessionManager将Session 的创建、修改、查询、删除委托给SessionDAO,
然后通过 SessionValidationScheduler 来创建一个守护线程,定时扫描会话是否过期或失效。失效时,如果设置失效会话可以删除,则调用sessionDAO.delete()删除.