Apache Shiro Session 管理

前言

shiro 提供了 session 用于保存与用户相关的信息,需要注意到这和 HTTP 的 session 概念是不一样的,它可以运行在没有web环境下。本篇文章会先介绍 Web 环境下的原理,读者有兴趣的话,可以看看在非 Web 环境下的原理。

Session 接口

classDiagram
	class Session
	<<interface>> Session
	
	class ValidatingSession
	<<interface>> ValidatingSession
	
	Session <|-- ValidatingSession
	
	class SimpleSession
	ValidatingSession <|-- SimpleSession
	
	class DelegatingSession
	Session <|-- DelegatingSession
	
	class HttpServletSession
	Session <|-- HttpServletSession
	
	class ProxiedSession
	Session <|-- ProxiedSession

Session 定义了主要接口,支持设置时间有效期,存储等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public interface Session {
    
    // 返回 sessionId
	Serializable getId();
    
    // 设置超时时间
    void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;
    
    // 添加值
    void setAttribute(Object key, Object value) throws InvalidSessionException;
    
    Object getAttribute(Object key) throws InvalidSessionException;
    
	// ....
}

HttpServletSession 实现了 http 的 session,运行在 web 环境。

DelegatingSession支持本地管理 session,运行在非 web 环境。

ProxiedSession只是Session的代理类。

SimpleSession只是简单的Session实现类。

Session 管理

session 管理分为两类,非 Web 环境和 Web 环境。它负责 session 的创建,查找,删除等管理操作,由SessionManager接口表示。

SessionManager 接口

SessionManager只有两个方法,创建和查找 session。通过这两个方法的声明,可以看到多个概念的联系。

1
2
3
4
5
6
7
8
public interface SessionManager {
    
    // 创建Session实例
    Session start(SessionContext context);
    
    // 获取Session实例
    Session getSession(SessionKey key) throws SessionException;
}

上述方法涉及到了 SessionSessionContextSessionKeySessionManager四种概念,这里先简单介绍下它们,

  • Session存储了用户的相关信息
  • SessionContext用作初始化Session实例
  • SessionKeySession实例的 id,用于查找
  • SessionManager管理着多个Session

SessionManager 类图

session 管理根据使用环境分为 HTTP 和 非 HTTP 两类。下图中左边的部分是使用本地 session 管理,在 Web 环境和非 Web 环境下都可以试用。右边的是使用Servlet自带的 session 管理,只能在 Web 环境下使用。

classDiagram
	class SessionManager
	<<interface>> SessionManager
	
	class NativeSessionManager
	<<interface>> NativeSessionManager
	
	SessionManager <|-- NativeSessionManager
	
	class AbstractSessionManager
	<<abstract>> AbstractSessionManager
	
	SessionManager <|-- AbstractSessionManager
	
	class ValidatingSessionManager
	<<interface>> ValidatingSessionManager
	
	SessionManager <|-- ValidatingSessionManager
	
	class AbstractNativeSessionManager
	<<abstract>> AbstractNativeSessionManager
	
	class AbstractValidatingSessionManager
	<<abstract>> AbstractValidatingSessionManager
	
    AbstractSessionManager <|-- AbstractNativeSessionManager

	AbstractNativeSessionManager <|-- AbstractValidatingSessionManager
	NativeSessionManager <|-- AbstractNativeSessionManager
	ValidatingSessionManager <|-- AbstractValidatingSessionManager
	
	class DefaultSessionManager
	AbstractValidatingSessionManager <|-- DefaultSessionManager
	
	AbstractNativeSessionManager <|-- AbstractValidatingSessionManager
    ValidatingSessionManager <|-- AbstractValidatingSessionManager
    
    class WebSessionManager
    <<interface>> WebSessionManager
    SessionManager <|-- WebSessionManager
    WebSessionManager <|-- ServletContainerSessionManager
    
    class DefaultWebSessionManager
    DefaultSessionManager <|-- DefaultWebSessionManager
    WebSessionManager <|-- DefaultWebSessionManager

NativeSessionManager定义了本地 session 管理的接口,也就是说它可以存在非 web 环境下。

DefaultSessionManager是用于非 Web 环境下, 默认的管理器。

DefaultWebSessionManager是 shrio 支持用于 Web 环境下,只不过使用了自己的 session 管理。

ServletContainerSessionManager是基于 spring web 实现的,只能用于 Web 环境下,它只是简单的封装了Serlvet相关功能。

Web 环境

因为 Web 的场景会非常普遍,而且实现也比较简单,所以会先讲解这部分。ServletContainerSessionManager类实现了SessionManager接口,下面是它的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ServletContainerSessionManager implements WebSessionManager {
    
    // 实现start接口,创建Session实例
    public Session start(SessionContext context) throws AuthorizationException {
        return createSession(context);
    }
    
    protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
        // 这里sessionContext实际是DefaultWebSessionContext类型,从中提取HttpServletRequest
        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);
		// 从中提取HttpSession
        HttpSession httpSession = request.getSession();

        // 构建HttpServletSession实例
        String host = getHost(sessionContext);
        return createSession(httpSession, host);
    }
    
    protected Session createSession(HttpSession httpSession, String host) {
        return new HttpServletSession(httpSession, host);
    }
    
    // 获取 session
    public Session getSession(SessionKey key) throws SessionException {
        HttpServletRequest request = WebUtils.getHttpRequest(key);
        Session session = null;
        // 从HttpServletRequest获取session
        HttpSession httpSession = request.getSession(false);
        if (httpSession != null) {
            session = createSession(httpSession, request.getRemoteHost());
        }

        return session;
    }
    
}

可以看到,它只是简单的调用了HttpServletRequest相关的方法,创建了HttpServletSession实例。HttpServletSession也只是简单的包装了一下HTTPSession

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class HttpServletSession implements Session {
    
    private HttpSession httpSession = null;
    
    // 返回 session id
    public Serializable getId() {
        return httpSession.getId();
    }
    
    // 获取session里的值
    public Object getAttribute(Object key) throws InvalidSessionException {
        return httpSession.getAttribute(assertString(key));
    }
    
    // 设置session里的值
    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        httpSession.setAttribute(assertString(key), value);
    }
}

非 Web 环境

DefaultSessionManager是非 Web 环境下默认的 session 管理器。我们来看看它的实现原理,因为它继承了AbstractNativeSessionManager类,所以真正的创建 session 实例由doCreateSession方法负责。从下面的代码可以看到,session的创建是有工厂类SessionFactory负责,并且创建后还会将其持久化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DefaultSessionManager extends AbstractValidatingSessionManager {
    
    // 负责实例化 session
    private SessionFactory sessionFactory;
    
    // session 持久化
    protected SessionDAO sessionDAO;
    
 	protected Session doCreateSession(SessionContext context) {
        // 使用SessionFactory创建实例
        Session s = newSessionInstance(context);
        // 使用SessionDAO持久化
        create(s);
        return s;
    }
    
    protected Session newSessionInstance(SessionContext context) {
        return getSessionFactory().createSession(context);
    }
    
     protected void create(Session session) {
        sessionDAO.create(session);
    }
}

同样根据 sessionId 查找 session 的方法,最终由retrieveSession实现。可以看到session的查找最终是由SessionDAO负责。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class DefaultSessionManager extends AbstractValidatingSessionManager {
    
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);
        // 调用SessionDAO来寻找session
        Session s = retrieveSessionFromDataSource(sessionId);
        return s;
    }
    
    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
        return sessionDAO.readSession(sessionId);
    }
}

Session 实例化

上面涉及到了SessionFactory工厂类,用作创建 session。接下来就讲讲它的原理。

SessionFactory 工厂类

SessionFactory接口定义了创建 Session 的方法createSession,其中参数SessionContext是用作初始化的。

1
2
3
public interface SessionFactory {
    Session createSession(SessionContext initData);
}

目前只有SimpleSessionFactory类实现了该接口,它的实现非常简单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class SimpleSessionFactory implements SessionFactory {

    // 这里只是存储了客户端的host
    public Session createSession(SessionContext initData) {
        if (initData != null) {
            String host = initData.getHost();
            if (host != null) {
                return new SimpleSession(host);
            }
        }
        return new SimpleSession();
    }
}

初始化参数SessionContext

SessionContext的相关类图如下所示,虽然比较多,但是原理很简单。

classDiagram

	class Map
	<<interface>> Map
	
    class SessionContext
    <<interface>> SessionContext
    class MapContext

	Map <|-- SessionContext
    Map <|-- MapContext
    
    class RequestPairSource
    <<interface>> RequestPairSource
    
    class WebSessionContext
    <<interface>> WebSessionContext
        
    SessionContext <|-- WebSessionContext
    RequestPairSource <|-- WebSessionContext

    class DefaultSessionContext
    
    MapContext <|-- DefaultSessionContext
    SessionContext <|-- DefaultSessionContext

    class DefaultWebSessionContext
    DefaultSessionContext <|-- DefaultWebSessionContext
    WebSessionContext <|-- DefaultWebSessionContext

SessionContext继承 Map接口,并且定义了 host 和 sessionId 两个属性的操作方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public interface SessionContext extends Map<String, Object> {
    
    void setHost(String host);
    
    String getHost();
    
    Serializable getSessionId();
    
    void setSessionId(Serializable sessionId);
}

DefaultSessionContext实现了默认的SessionContext接口。

DefaultWebSessionContext在它基础之上,添加了获取ServletRequestServletResponse实例的方法。

Session 持久化

SessionDAO定义了 session 增删改查接口,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public interface SessionDAO {
    
    Serializable create(Session session);

    Session readSession(Serializable sessionId) throws UnknownSessionException;

    void update(Session session) throws UnknownSessionException;

    void delete(Session session);

    Collection<Session> getActiveSessions();
}

它的子类图如下所示:

classDiagram
	class SessionDAO
	<<interface>> SessionDAO
	
	class AbstractSessionDAO
	<<abstract>> AbstractSessionDAO
	
	SessionDAO <|-- AbstractSessionDAO
	
	class CachingSessionDAO
	<<abstract>> CachingSessionDAO
	
	AbstractSessionDAO <|-- CachingSessionDAO
	
	class MemorySessionDAO
	AbstractSessionDAO <|-- MemorySessionDAO
	
	class EnterpriseCacheSessionDAO
	CachingSessionDAO <|-- EnterpriseCacheSessionDAO

AbstractSessionDAO实现了SessionDAO接口,并且定义了doCreate接口,子类需要实现它并且返回 SessionId

MemorySessionDAO使用了 ConcurrentMap来保存 session。

CachingSessionDAO实现了session缓存,用户可以集成CacheManager实现自定义的缓存。

当持久化化 session 的时候,SessionDAO会使用SessionIdGenerator接口生成唯一的 SessionId

classDiagram
	class SessionIdGenerator
	<<interface>> SessionIdGenerator
	
	class RandomSessionIdGenerator
	class JavaUuidSessionIdGenerator
	
	SessionIdGenerator <|-- RandomSessionIdGenerator
	SessionIdGenerator <|-- JavaUuidSessionIdGenerator

RandomSessionIdGenerator会生成随机的 sessionId,JavaUuidSessionIdGenerator会生成 uuid。

Session 集成

我们再回到DefaultSecurityManager类,看看它是如何集成Session的。SessionsSecurityManager是它的父类,它默认使用DefaultSessionManager实现,实现了 session 管理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
    
    private SessionManager sessionManager;
    
    // 使用DefaultSessionManager作为默认的实现
    public SessionsSecurityManager() {
        super();
        this.sessionManager = new DefaultSessionManager();
    }
    
    // 转发给sessionManager处理
    public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context);
    }

    // 转发给sessionManager处理
    public Session getSession(SessionKey key) throws SessionException {
        return this.sessionManager.getSession(key);
    }
}
updatedupdated2023-07-022023-07-02