zoukankan      html  css  js  c++  java
  • 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

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

    ---栖息之鹰(一个外表懒洋洋的内心有激情的程序员) 此博客为笔者原著,转载时请注明出处,谢谢!
  • 相关阅读:
    0827IO作业
    0927集合作业
    初学集合,以及泛型
    异常课——抛出
    Python环境变量配置
    安装Python
    MySQL多表操作
    MySQL增删改查
    Group by分组详解
    MySQL常用函数
  • 原文地址:https://www.cnblogs.com/roostinghawk/p/10716285.html
Copyright © 2011-2022 走看看