Shiro(2) 会话管理

我们知道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()删除.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值