Shiro源码解析-Session篇

上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下。

1. 分析切入点:DefaultSecurityManger的login方法

    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }

        Subject loggedIn = createSubject(token, info, subject); // 登录用户验证成功之后进行session处理

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

继续DefaultSecurityManger

    protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
        SubjectContext context = createSubjectContext();
        context.setAuthenticated(true);
        context.setAuthenticationToken(token);
        context.setAuthenticationInfo(info);
        if (existing != null) {
            context.setSubject(existing);
        }
        return createSubject(context); // 此处创建Subject
    }

DefaultSecurityManger的createSubject方法

    public Subject createSubject(SubjectContext subjectContext) {
        //create a copy so we don't modify the argument's backing map:
        SubjectContext context = copy(subjectContext);

        //ensure that the context has a SecurityManager instance, and if not, add one:
        context = ensureSecurityManager(context);

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
        //process is often environment specific - better to shield the SF from these details:
        context = resolveSession(context);

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
        //if possible before handing off to the SubjectFactory:
        context = resolvePrincipals(context);

        Subject subject = doCreateSubject(context);

        //save this subject for future reference if necessary:
        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
        //Added in 1.2:
        save(subject); // 在这一步存储session

        return subject;
    }

    protected void save(Subject subject) {
       this.subjectDAO.save(subject);
    }

 2. 转移到DefaultSubjectDAO
  调用DefaultSubjectDAO.save(subject)方法

    public Subject save(Subject subject) {
        if (isSessionStorageEnabled(subject)) { // 默认sessionStorage是enabled
            saveToSession(subject); // 看这里
        } else {
            log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
                    "authentication state are expected to be initialized on every request or invocation.", subject);
        }

        return subject;
    }

  调用DefaultSubjectDAO.saveToSession(subject)方法

   protected void saveToSession(Subject subject) {
        //performs merge logic, only updating the Subject's session if it does not match the current state:
        mergePrincipals(subject);
        mergeAuthenticationState(subject);
    }

调用DefaultSubjectDAO的mergePrincipals方法

   protected void mergePrincipals(Subject subject) {
        //merge PrincipalCollection state:

        PrincipalCollection currentPrincipals = null;

        //SHIRO-380: added if/else block - need to retain original (source) principals
        //This technique (reflection) is only temporary - a proper long term solution needs to be found,
        //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
        //
        //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
        if (subject.isRunAs() && subject instanceof DelegatingSubject) {
            try {
                Field field = DelegatingSubject.class.getDeclaredField("principals");
                field.setAccessible(true);
                currentPrincipals = (PrincipalCollection)field.get(subject);
            } catch (Exception e) {
                throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
            }
        }
        if (currentPrincipals == null || currentPrincipals.isEmpty()) {
            currentPrincipals = subject.getPrincipals();
        }

        Session session = subject.getSession(false); // 取得session,如果不存在,并不会主动创建session

        if (session == null) {
            if (!isEmpty(currentPrincipals)) {
                session = subject.getSession();  // 第一次用户访问时,会创建session,那么session是如何创建的呢?在缓存还是在DB中,请继续往下看
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
            }
            // otherwise no session and no principals - nothing to save
        } else {
            PrincipalCollection existingPrincipals =
                    (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

            if (isEmpty(currentPrincipals)) {
                if (!isEmpty(existingPrincipals)) {
                    session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                }
                // otherwise both are null or empty - no need to update the session
            } else {
                if (!currentPrincipals.equals(existingPrincipals)) {
                    session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
                }
                // otherwise they're the same - no need to update the session
            }
        }
    }

3. 转移到DelegatingSubject

   调用DelegatingSubject的getSession 

    public Session getSession() {
        return getSession(true); // 参数为true,表示不存在此session时创建
    }

 public Session getSession(boolean create) {
        if (log.isTraceEnabled()) {
            log.trace("attempting to get session; create = " + create +
                    "; session is null = " + (this.session == null) +
                    "; session has id = " + (this.session != null && session.getId() != null));
        }

        if (this.session == null && create) {

            //added in 1.2:
            if (!isSessionCreationEnabled()) {
                String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
                        "that there is either a programming error (using a session when it should never be " +
                        "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
                        "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
                        "for more.";
                throw new DisabledSessionException(msg);
            }

            log.trace("Starting session for host {}", getHost());
            SessionContext sessionContext = createSessionContext();
            Session session = this.securityManager.start(sessionContext); // session创建,从这里再往下走(这里的securityMananger是SessionSecurityManager)
            this.session = decorate(session);
        }
        return this.session;
    }

4. 转移到SessionSecurityManager

    public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context); // 如果没有设置sessionManager,则调用默认的ServletContainerSessionManager(内存存储session),demo代码使用如下
    }

5. 如果像demo中soure一样,设置了的sessionManager为DefaultWebSessionManager,那么接下来会转移为到它的父类AbstractValidatingSessionManager

    protected Session createSession(SessionContext context) throws AuthorizationException {
        enableSessionValidationIfNecessary();
        return doCreateSession(context);
    }

   protected Session doCreateSession(SessionContext context) {
        Session s = newSessionInstance(context);
        if (log.isTraceEnabled()) {
            log.trace("Creating session for host {}", s.getHost());
        }
        create(s); // 在此处创建Session
        return s;
    }

    protected Session newSessionInstance(SessionContext context) {
        return getSessionFactory().createSession(context);
    }

 6. 调用DefaultSessionManager

    protected void create(Session session) {
        if (log.isDebugEnabled()) {
            log.debug("Creating new EIS record for new session instance [" + session + "]");
        }
        sessionDAO.create(session); // 到这里大家应该看到了,你配置的SessionDAO在什么时候调用,demo中使用EnterpriseCacheSessionDAO
    }

7. 先调用的父类CachingSessionDAO的create方法

    public Serializable create(Session session) {
        Serializable sessionId = super.create(session);// 这里只是生成sessionId,至于生成使用的算法可以在config中设置sessionIdGenerator
        cache(session, sessionId); // 生成session并存储到cache,过程在下面
        return sessionId;
    }

8. 接下来cache方法

    protected void cache(Session session, Serializable sessionId) {
        if (session == null || sessionId == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy(); // 创建cache,名字默认为shiro-activeSessionCache
        if (cache == null) {
            return;
        }
        cache(session, sessionId, cache); // 存储到cache
    }

   protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
cache.put(sessionId, session); // 这里调用的Ehcache的put方法,最终是存储在cache中(当然,如果你自定义了SessionDAO,那就可以存储在你指定的地方)
}

到这里大家基本都明白了整个过程吧,通过源码分析我们可以明白以下关键点

  • SessionManager什么时候调用
  • SessionDAO何时调用
  • SessionId什么时候生成
  • Session什么时候存储到Cache

如有问题,欢迎评论回复!

---栖息之鹰(一个外表懒洋洋的内心有激情的程序员) 此博客为笔者原著,转载时请注明出处,谢谢!
原文地址:https://www.cnblogs.com/roostinghawk/p/10716285.html