shiro源碼分析04 -- SubjectDAO

前面的章節說完了Subject的創建過程,創建完Subject需要將其保存起來,而保存的過程則由SubjectDAO完成。既然每一次訪問都會創建一個新的Subject,那麼爲什麼還需要SubjectDAO來存儲呢?其實SubjectDAO做的事情很少,沒有存儲Subject,只是將當前Subject的authenticated(是否校驗成功)和principal(身份)放入到session中。

Subject的生命週期:

  1. 每一次訪問都會創建一個新的subject對象。
  2. 將創建的subject對象綁定到ThreadContext上,之後通過SecurityUtils便可以在程序的任何地方獲取到Subject對象。
  3. 每一次創建完成subject,都會通過SubjectDAO將subject屬性存放到session中去,但是隻存放principal(身份)和authenticated(是否校驗成功)。

 org.apache.shiro.mgt.DefaultSecurityManager中createSubject創建完Subject,調用SubjectDAO保存Subject

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);

        return subject;
}


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

SubjectDAO是一個接口,主要包含save和delete兩個方法操作Subject,其主要實現類是DefaultSubjectDAO

public interface SubjectDAO {

    /**
     * Persists the specified Subject's state for later access.  If there is a no existing state persisted, this
     * persists it if possible (i.e. a create operation).  If there is existing state for the specified {@code Subject},
     * this method updates the existing state to reflect the current state (i.e. an update operation).
     *
     * @param subject the Subject instance for which its state will be created or updated.
     * @return the Subject instance to use after persistence is complete.  This can be the same as the method argument
     * if the underlying implementation does not need to make any Subject changes.
     */
    Subject save(Subject subject);

    /**
     * Removes any persisted state for the specified {@code Subject} instance.  This is a delete operation such that
     * the Subject's state will not be accessible at a later time.
     *
     * @param subject the Subject instance for which any persistent state should be deleted.
     */
    void delete(Subject subject);
}

通過org.apache.shiro.mgt.DefaultSubjectDAO中的save()方法,通過判斷isSessionStorageEnabled()將subject的屬性保存到session中去。但是決定是否進行保存是由sessionStorageEvaluator這個參數進行控制的。

/**
     * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only
     * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}.  If session storage is not enabled
     * for the specific {@code Subject}, this method does nothing.
     * <p/>
     * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created).
     *
     * @param subject the Subject instance for which its state will be created or updated.
     * @return the same {@code Subject} passed in (a new Subject instance is not created).
     */
    public Subject save(Subject subject) {
        if (isSessionStorageEnabled(subject)) {
            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;
    }


    protected boolean isSessionStorageEnabled(Subject subject) {
        return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
    }


    public SessionStorageEvaluator getSessionStorageEvaluator() {
        return sessionStorageEvaluator;
    }

SessionStorageEvaluator其主要實現類是DefaultSessionStorageEvaluator

public interface SessionStorageEvaluator {

    /**
     * Returns {@code true} if the specified {@code Subject}'s
     * {@link org.apache.shiro.subject.Subject#getSession() session} may be used to persist that Subject's
     * state, {@code false} otherwise.
     *
     * @param subject the {@code Subject} for which session state persistence may be enabled
     * @return {@code true} if the specified {@code Subject}'s
     *         {@link org.apache.shiro.subject.Subject#getSession() session} may be used to persist that Subject's
     *         state, {@code false} otherwise.
     * @see Subject#getSession()
     * @see Subject#getSession(boolean)
     */
    boolean isSessionStorageEnabled(Subject subject);

}

 決定策略就是通過DefaultSessionStorageEvaluator的sessionStorageEnabled的狀態和Subject是否存在Session決定的

public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator {

    private boolean sessionStorageEnabled = true;


    public boolean isSessionStorageEnabled(Subject subject) {
        return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();
    }

    public boolean isSessionStorageEnabled() {
        return sessionStorageEnabled;
    }

    public void setSessionStorageEnabled(boolean sessionStorageEnabled) {
        this.sessionStorageEnabled = sessionStorageEnabled;
    }
}

 如果允許存儲subject的session,則執行以下的具體存儲過程:

/**
     * Saves the subject's state (it's principals and authentication state) to its
     * {@link org.apache.shiro.subject.Subject#getSession() session}.  The session can be retrieved at a later time
     * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
     * the {@code Subject} instance.
     *
     * @param subject the subject for which state will be persisted to its session.
     */
    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);
    }

    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);

        if (session == null) {
            if (!isEmpty(currentPrincipals)) {
                session = subject.getSession();
                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
            }
        }
    }

    protected void mergeAuthenticationState(Subject subject) {

        Session session = subject.getSession(false);

        if (session == null) {
            if (subject.isAuthenticated()) {
                session = subject.getSession();
                session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
            }
            //otherwise no session and not authenticated - nothing to save
        } else {
            Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);

            if (subject.isAuthenticated()) {
                if (existingAuthc == null || !existingAuthc) {
                    session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
                }
                //otherwise authc state matches - no need to update the session
            } else {
                if (existingAuthc != null) {
                    //existing doesn't match the current state - remove it:
                    session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
                }
                //otherwise not in the session and not authenticated - no need to update the session
            }
        }
    }

 上面重點關注,當subject.getSession(false)獲取的Session爲空時(它不會去創建Session),此時就需要去創建Session,subject.getSession()則默認調用的是subject.getSession(true),則會進行Session的創建。

public Session getSession() {
        return getSession(true);
    }

    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);
            this.session = decorate(session);
        }
        return this.session;
    }

使用session來持久化保存Subject的principal(身份)和authenticated(是否校驗成功)。在Session中保存Subject信息後,通過cookie上送的sessionId,shiro便可以根據session來重建Subject實例。

用戶每次請求,均使用session重建subject對象,那麼我們可以修改session中的身份信息,以實現用戶非重新登錄,便可獲取到最新的身份信息principal。

源碼參考:org.apache.shiro.mgt.DefaultSubjectDAO#mergePrincipals

但是在無狀態認證中,請求每次攜帶token,每次都要經過身份認證。通常就不需要session來存儲此狀態。便可以禁止存儲,如下代碼,禁止session存儲subject信息:

    @Bean
    public DefaultSubjectDAO defaultSubjectDAO() {
        return new DefaultSubjectDAO() {
            @Override
            protected boolean isSessionStorageEnabled(Subject subject) {
                return false;
            }
        };
    }

參考文章:https://www.jianshu.com/p/e2f5e231a2f7https://blog.csdn.net/hxpjava1/article/details/77917106

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章