Bootstrap

Shrio中的session机制

主要是web相关,javase不说

引入

SecurityManager是shiro的核心,负责与shiro的其他组件进行交互,而SessionManager是session的真正管理者,负责shiro的session管理。

在shrio中,当我们利用subject.login()进行登录的时候或者是利用subject.getSession()的时候,就会创建一个session。这个session与servlet中的session是完全独立的,是shrio内部自己实现的一个session机制,但是器与servlet的session的理念基本相同。
利用subject登录的时候会创建session对象,不然怎么维持用户的登录状态呢?主动获取session的时候会创建session对象,这是肯定的嘛。

Shiro是如何维持Session的

在web中,我们默认使用的SecurityManagerDefaultWebSecurityManager,与之对应的默认使用的sessionManager就是ServletContainerSessionManager。从他的名字我们就能看出来,其默认是以servlet容器来存储session,也就是说shrio默认情况下的session还是依赖servlet容器的。查看其源码我们能发现,ServletContainerSessionManager管理的session,就是代理了HttpSession,HttpSession还是交给Servlet容器进行管理的。
DefaultWebSessionManager
这是shrio提供的另外一个session管理器,这个session管理器是完全独立的,不依赖于任何容器,在这个管理器的支持下,我们可以实现将session存储到redis。
HttpSession中保存sessionId是通过Cookie,DefaultWebSessionManager也是如此,这是一段DefaultWebSessionManager中的代码

// 存储sessionId到cookie中
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
    if (currentId == null) {
        String msg = "sessionId cannot be null when persisting for subsequent requests.";
        throw new IllegalArgumentException(msg);
    }
    Cookie template = getSessionIdCookie();
    Cookie cookie = new SimpleCookie(template);
    String idString = currentId.toString();
    cookie.setValue(idString);
    // 将session保存到了response中
    cookie.saveTo(request, response);
}
// 从cookie中读取sessionId
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
    if (!isSessionIdCookieEnabled()) {
        log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
        return null;
    }
    if (!(request instanceof HttpServletRequest)) {
        log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
        return null;
    }
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
}

当我们在一次请求中第一次获取Subject的时候,其往往会解析出当前subject所关联的session,这样才能知道你是谁嘛,不过前提是之前存在session,否则还是等到我们手动获取session才进行创建。其解析就交给了我们的各种sessionManager,DefaultWebSessionManager就是从cookie中进行解析的。

session保存在哪

ServletContainerSessionManager因为利用的是Servlet容器中的HttpSession,所以就是由servlet容器保管,DefaultWebSessionManager呢?这就要引出一个新的东西SessionDAO
DefaultWebSessionManager是不管保存在哪个位置的,其是交给sessionDao来进行保存,通过sessionDao接口我们可以实现保存在任何地方,其中就包括redis

// 非常明显的增删改查
public interface SessionDAO {
    Serializable create(Session session);
    Session readSession(Serializable sessionId) throws UnknownSessionException;
    void update(Session session) throws UnknownSessionException;
    void delete(Session session);
    // 返回活跃的session
    Collection<Session> getActiveSessions();
}

DefaultWebSessionManager中默认使用的sessionDao是MemorySessionDAO,其利用一个ConcurrentMap来保存Session。
如果我们要实现一个自定义的SessionDao,一般是建议实现EnterpriseCacheSessionDAO,因为其内部已经有了很多默认实现,我们需要修改的就比较少

Session过期

上面看起来一切都好,但是好像少了个重要的东西,那就是session的过期机制。一般的过期机制都是使用定时任务来进行处理的,Shrio也不例外。

AbstractValidatingSessionManager类中有如下方法,获取session时会经过此方法

@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
    // 启动session有效性的验证,也就是创建一个定时任务了
    enableSessionValidationIfNecessary();
    ...
}

在具体往下追溯就可以追溯到一个类ExecutorServiceSessionValidationScheduler,中间的一些小的东西就跳过了
这是其内部的run方法,想必已经相当的明显了
定时任务的间隔是1个小时

public void run() {
    if (log.isDebugEnabled()) {
        log.debug("Executing session validation...");
    }
    long startTime = System.currentTimeMillis();
    // 调用sessionManager的效验方法
    this.sessionManager.validateSessions();
    long stopTime = System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
    }
}

shiro的Session接口提供了一个touch方法,负责session的刷新。但是touch方法是什么时候被调用的呢?JavaSE需要我们自己定期的调用session的touch() 去更新最后访问时间;如果是Web应用,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间
AbstractShiroFilter类中

protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
    if (!isHttpSessions()) { //'native' sessions
        Subject subject = SecurityUtils.getSubject();
        if (subject != null) {
            Session session = subject.getSession(false);
            if (session != null) {
                try {
                    session.touch();
                } catch (Throwable t) {
                    log.error("session.touch() method invocation has failed.  Unable to update" +
                            "the corresponding session's last access time based on the incoming request.", t);
                }
            }
        }
    }
}
;