Shiro1.x中SessionManager是如何处理的

浏览:1070 发布日期:2024-04-16 14:48:45

Shiro 1.x对session的管理,都是使用SessionManager,忽略SecurityManager的部分,我们看它的继承类图结构:

17132640916975660250086.jpg

SessionManager接口

它提供两个方法,startgetSession

  1. Session start(SessionContext context)

    根据指定的上下文初始化数据启动一个新会话,底层实现可以使用该数据来确定如何准确地创建内部 Session 实例。

  2. Session getSession(SessionKey key) throws SessionException

    检索与指定上下文数据(例如会话 ID,如果适用)相对应的会话,如果找不到 Session,则返回 null。 如果找到会话但无效(已停止或过期),则会抛出 SessionException。

WebSessionManager

public interface WebSessionManager extends SessionManager
  1. boolean isServletContainerSessions()

    如果会话管理和存储由底层 Servlet 容器管理,则返回 true;如果由 Shiro 直接管理(称为“本机”会话),则返回 false。

ServletContainerSessionManager类
public class ServletContainerSessionManager implements WebSessionManager

SessionManager 实现提供 Session 实现,这些实现仅仅是 Servlet 容器的 HttpSession 的包装器。

尽管它的名称如此,此实现本身并不管理会话,因为 Servlet 容器提供了实际的管理支持。 这个类的存在主要是为了“模拟”常规的 ShiroSessionManager,这样它就可以插入到纯 Web 应用程序中的常规 Shiro配置中。

请注意,由于此实现依赖于 HttpSession,因此它仅在 servlet 容器中起作用 - 它无法支持除使用 HTTP 协议之外的任何客户端的会话。

因此,如果您需要异构客户端(例如 Web 浏览器、RMI 客户端等)的会话支持,请改用 DefaultWebSessionManagerDefaultWebSessionManager 支持传统的基于 Web 的访问以及非基于 Web 的客户端。

AbstractSessionManager类

public abstract class AbstractSessionManager implements SessionManager

这个类仅添加了超时时间的相关字段和方法,默认超时30分钟。getGlobalSessionTimeout()setGlobalSessionTimeout(long globalSessionTimeout)方法。

ValidatingSessionManager接口

public interface ValidatingSessionManager extends SessionManager

ValidatingSessionManager 是一个可以主动验证任何或所有可能过期的会话的 SessionManager

  1. void validateSessions()

    这个方法用于验证session是否过期。

NativeSessionManager接口

public interface NativeSessionManager extends SessionManager

本机会话管理器是一种本地管理会话的管理器 - 也就是说,它直接负责Session 实例及其生命周期的创建、持久化和删除。

它提供session相关操作。

AbstractNativeSessionManager类

public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware

支持NativeSessionManager接口的抽象实现,支持SessionListenersglobalSessionTimeout的应用。

AbstractValidatingSessionManager类

public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
    implements ValidatingSessionManager, Destroyable

ValidatingSessionManager 接口的默认业务层实现。

这是一个抽象类,涉及读取session、创建session的方法都没有实现,留给子类来实现。

  1. protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException

    根据指定的会话密钥从底层数据存储中查找会话。

  2. protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException

DefaultSessionManager类

public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware

ValidatingSessionManager 的默认业务层实现。 所有会话 CRUD 操作都委托给内部 SessionDAO

这里有两个很关键的字段SessionDAOCacheManager

默认构造函数,SessionDAO默认实现是MemorySessionDAO

public DefaultSessionManager() {
    this.deleteInvalidSessions = true;
    this.sessionFactory = new SimpleSessionFactory();
    this.sessionDAO = new MemorySessionDAO();
}

由于这个类实现了CacheManagerAware接口,所以它可以自动设置CacheManager。并自动为sessionDAO设置CacheManager

public void setCacheManager(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
    applyCacheManagerToSessionDAO();
}

private void applyCacheManagerToSessionDAO() {
    if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
        ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
    }
}

DefaultWebSessionManager类

public class DefaultWebSessionManager extends DefaultSessionManager implements WebSessionManager

支持 Web 应用程序的 SessionManager 实现。

这个类相比DefaultSessionManager只是添加了cookie相关的方法。

========================================

因为DefaultSessionManager类涉及session操作都委托到了SessionDAO,所以我们来看一下它的类图:

SessionDAO接口

数据访问对象设计模式规范,用于启用对 EIS(企业信息系统)的会话访问。 它提供了四种典型的 CRUD 方法:createreadSession(Serialized)update(Session) delete(Session)

剩下的 getActiveSessions() 方法作为抢占式孤立会话的支持机制而存在,通常由 ValidatingSessionManagers 实现),并且应该尽可能高效,特别是在有数千个活动会话的情况下。 大规模/高性能实现通常会返回总活动会话的子集并更频繁地执行验证,而不是返回大量集合并很少进行验证。

  1. Serializable create(Session session)方法

    这个方法根据一个Session对象生成session id,这个session参数是新创建的,基本是没有id的。

  2. Session readSession(Serializable sessionId) throws UnknownSessionException

    根据id读取session

  3. void update(Session session) throws UnknownSessionException

    更新session

  4. void delete(Session session)

    删除session

  5. Collection<Session> getActiveSessions()

    返回 EIS 中被视为活动的所有会话,这意味着所有尚未停止/过期的会话。 这主要用于验证潜在的孤儿。

    如果 EIS 中没有活动会话,则此方法可能返回空集合或 null。 表现 此方法应该尽可能高效,特别是在可能有数千个活动会话的大型系统中。 大规模/高性能实现通常会返回总活动会话的子集并更频繁地执行验证,而不是返回大量集合并很少进行验证。 如果高效且可能,则返回最旧的未停止可用会话(按 LastAccessTime 排序)是有意义的。 智能结果 理想情况下,此方法只会返回 EIS 确定应无效的活动会话。 通常,这是任何未停止且其 LastAccessTimestamp 早于会话超时的会话。

    例如,如果会话由关系数据库或 SQL-92“可查询”企业缓存支持,您可能会返回与此查询返回的结果类似的内容(假设正在存储SimpleSessions):select * from session s where s.lastAccessTimestamp < ? and s.stopTimestamp is null`

    哪里的? 参数是一个日期实例,等于“现在”减去会话超时(例如现在 - 30 分钟)。 返回值: 被视为活动会话的集合,如果没有活动会话,则为空集合或 null。

AbstractSessionDAO类

public abstract class AbstractSessionDAO implements SessionDAO

一个抽象的 SessionDAO 实现,它对会话创建和读取执行一些健全性检查,并在需要时允许可插入的会话 ID 生成策略。 SessionDAO 更新和删除方法留给子类。

会话 ID 生成

该类还允许插入 SessionIdGenerator 以实现自定义 ID 生成策略。 这是可选的,因为默认生成器可能足以满足大多数情况。 使用生成器(默认或自定义)的子类实现将希望从其 doCreate 实现中调用generateSessionId(Session)方法。

依赖 EIS 数据存储自动生成 ID 的子类实现(例如,当会话 ID 也是自动生成的主键时),它们可以完全忽略 SessionIdGenerator 概念,只从doCreate 实现返回数据存储的 ID。

它重写了create方法,并委托给doCreate方法来生成id,然后验证id:

public Serializable create(Session session) {
    Serializable sessionId = doCreate(session);
    verifySessionId(sessionId);
    return sessionId;
}

protected abstract Serializable doCreate(Session session)

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)方法,为session添加id。

重写了readSession方法,并把它委托给doReadSession方法:

public Session readSession(Serializable sessionId) throws UnknownSessionException {
    Session s = doReadSession(sessionId);
    if (s == null) {
        throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
    }
    return s;
}

protected abstract Session doReadSession(Serializable sessionId)

CachingSessionDAO抽象类

public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware

CachingSessionDAO 是一个 SessionDAO,它在使用它的组件和底层 EIS(企业信息系统)会话支持存储(例如文件系统、数据库、企业网格/云等)之间提供透明的缓存层。

此实现将所有活动会话缓存在配置的 activeSessionsCache 中。 该属性默认为 null,如果未显式设置,则需要配置一个 cacheManager,该缓存管理器将用于获取用于 activeSessionsCache  Cache 实例。

所有 SessionDAO 方法均由此类实现,以采用缓存行为,并将实际的 EIS 操作委托给相应的 do* 方法以由子类(doCreatedoRead 等)实现。

它包含两个重要字段,CacheManagerCache<Serializable, Session>字段。

它重写了create方法,如果存在CacheManager,则会缓存到cache中。

public Serializable create(Session session) {
    Serializable sessionId = super.create(session);
    cache(session, sessionId);
    return sessionId;
}

重写了readSession方法,先尝试从cache中读取:

protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
    return cache.get(sessionId);
}

protected Session getCachedSession(Serializable sessionId) {
    Session cached = null;
    if (sessionId != null) {
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache != null) {
            cached = getCachedSession(sessionId, cache);
        }
    }
    return cached;
}

public Session readSession(Serializable sessionId) throws UnknownSessionException {
    Session s = getCachedSession(sessionId);
    if (s == null) {
        s = super.readSession(sessionId);
    }
    return s;
}

它重写了update方法,首先通过委托 doUpdate(Session) 将给定会话的状态更新到 EIS。 如果会话是 ValidatingSession,则仅当它是ValidatingSession.isValid() 时才会添加到缓存中,如果无效,则会从缓存中删除。 如果它不是 ValidatingSession 实例,则无论如何都会将其添加到缓存中。

protected abstract void doUpdate(Session session);

public void update(Session session) throws UnknownSessionException {
    doUpdate(session);
    if (session instanceof ValidatingSession) {
        if (((ValidatingSession) session).isValid()) {
            cache(session, session.getId());
        } else {
            uncache(session);
        }
    } else {
        cache(session, session.getId());
    }
}

它重写了delete方法:

protected void uncache(Session session) {
    if (session == null) {
        return;
    }
    Serializable id = session.getId();
    if (id == null) {
        return;
    }
    Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
    if (cache != null) {
        cache.remove(id);
    }
}

protected abstract void doDelete(Session session)

public void delete(Session session) {
    uncache(session);
    doDelete(session);
}

它重写了getActiveSessions()方法,返回系统中的所有活动会话。

此实现仅返回在 activeSessions 缓存中找到的会话。 子类实现可能希望重写此方法以以不同的方式检索它们,可能通过 RDBMS 查询或其他方式。

public Collection<Session> getActiveSessions() {
    Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
    if (cache != null) {
        return cache.values();
    } else {
        return Collections.emptySet();
    }
}

EnterpriseCacheSessionDAO类

public class EnterpriseCacheSessionDAO extends CachingSessionDAO

SessionDAO 实现依赖于企业缓存产品作为所有会话的 EIS 记录系统。 预计注入的 Cache 或 CacheManager 由企业缓存产品支持,该产品可以支持所有应用程序会话和/或为弹性数据存储提供磁盘分页。 生产说明 此实现默认使用基于内存映射的 CacheManager,这非常适合测试,但通常无法针对生产环境进行扩展,并且很容易导致 OutOfMemoryExceptions。 只是不要忘记使用生产级 CacheManager 配置*此类的实例,该 CacheManager 可以处理大量会话的磁盘分页,这样就可以了。

*如果你使用这样的CacheManager配置Shiro的SecurityManager实例,它将自动应用于此类的实例,并且你不需要在配置中显式设置它。 实施细节 此实现在很大程度上依赖于父类对企业缓存产品的所有存储操作的透明缓存行为。 因为父类使用 Cache 或 CacheManager 来执行缓存,并且缓存被视为记录系统,所以 doReadSession、doUpdate 和 doDelete 方法实现不需要做任何进一步的操作。 该类按照父类的要求实现了这些方法,但它们本质上什么也不做。

它的构造函数,设置一个AbstractCacheManager的匿名类:

public EnterpriseCacheSessionDAO() {
    setCacheManager(new AbstractCacheManager() {
        @Override
        protected Cache<Serializable, Session> createCache(String name) throws CacheException {
            return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
        }
    });
}

这个类doReadSessiondoUpdatedoDelete都是空方法。因为它们的父类方法,并不依赖这些方法,会从缓存中获取。

protected Session doReadSession(Serializable sessionId) {
    return null; //should never execute because this implementation relies on parent class to access cache, which
    //is where all sessions reside - it is the cache implementation that determines if the
    //cache is memory only or disk-persistent, etc.
}

protected void doUpdate(Session session) {
    //does nothing - parent class persists to cache.
}

protected void doDelete(Session session) {
    //does nothing - parent class removes from cache.
}

===================================

下面来看一下,spring boot中自动的配置Session信息:

默认的SessionDAOMemorySessionDAO

protected SessionDAO sessionDAO() {
    return new MemorySessionDAO();
}

默认的SessionManagerDefaultSessionManager

protected SessionManager sessionManager() {
    DefaultSessionManager sessionManager = new DefaultSessionManager();
    sessionManager.setSessionDAO(sessionDAO());
    sessionManager.setSessionFactory(sessionFactory());
    sessionManager.setDeleteInvalidSessions(sessionManagerDeleteInvalidSessions);
    return sessionManager;
}