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

 

 

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