1. 程式人生 > >Shiro使用和原始碼分析---5

Shiro使用和原始碼分析---5

getSubject分析

上一章看完了DefaultWebSecurityManager的建構函式,首先來分析getSubject函式。getSubject定義在AccessControlFilter中。

getSubject

    protected Subject getSubject(ServletRequest request, ServletResponse response) {
        return SecurityUtils.getSubject();
    }

再看SecurityUtils的getSubject函式,

    public static
Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }

ThreadContext相當於一個執行緒安全的類,ThreadContext的getSubject函式定義如下

    public static Subject getSubject() {
        return (Subject) get(SUBJECT_KEY);
    }

這裡的SUBJECT_KEY=ThreadContext.class.getName() + “_SUBJECT_KEY”;,其get函式定義在ThreadContext中,如下

    public static Object get(Object key) {
        if (log.isTraceEnabled()) {
            String msg = "get() - in thread ["
+ Thread.currentThread().getName() + "]"; log.trace(msg); } Object value = getValue(key); if ((value != null) && log.isTraceEnabled()) { String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]"; log.trace(msg); } return value; }

getValue函式定義在ThreadContext中,如下

    private static Object getValue(Object key) {
        Map<Object, Object> perThreadResources = resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
    }

這裡假設是第一次訪問該Subject,返回null。回到getSubject函式,這裡就需要呼叫buildSubject函式,首先看Builder的建構函式,定義在Subject介面中,如下

        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }

該建構函式就是簡單的呼叫了newSubjectContextInstance,定義在Subject介面中,如下

        protected SubjectContext newSubjectContextInstance() {
            return new DefaultSubjectContext();
        }

DefaultSubjectContext的建構函式沒有什麼內容。回到getSubject函式中,接下來看buildSubject,如下

        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }

這裡的securityManager就是前面構造的DefaultWebSecurityManager啦,subjectContext=DefaultSubjectContext。
createSubject定義在DefaultSecurityManager中,

    public Subject createSubject(SubjectContext subjectContext) {
        SubjectContext context = copy(subjectContext);
        context = ensureSecurityManager(context);
        context = resolveSession(context);
        context = resolvePrincipals(context);
        Subject subject = doCreateSubject(context);
        save(subject);
        return subject;
    }

下面一一分析這些函式。

copy

    protected SubjectContext copy(SubjectContext subjectContext) {
        return new DefaultSubjectContext(subjectContext);
    }

DefaultSubjectContext的建構函式就是簡單的將SubjectContext 賦值。

ensureSecurityManager

ensureSecurityManager定義在DefaultSecurityManager中,

    protected SubjectContext ensureSecurityManager(SubjectContext context) {
        if (context.resolveSecurityManager() != null) {
            log.trace("Context already contains a SecurityManager instance.  Returning.");
            return context;
        }
        log.trace("No SecurityManager found in context.  Adding self reference.");
        context.setSecurityManager(this);
        return context;
    }

這裡就是檢視SubjectContext是否有SecurityManager,如果沒有就進行設定。該context就是剛剛構造的DefaultSubjectContext,它的resolveSecurityManager如下

    public SecurityManager resolveSecurityManager() {
        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            if (log.isDebugEnabled()) {
                log.debug("No SecurityManager available in subject context map.  " +
                        "Falling back to SecurityUtils.getSecurityManager() lookup.");
            }
            try {
                securityManager = SecurityUtils.getSecurityManager();
            } catch (UnavailableSecurityManagerException e) {
                if (log.isDebugEnabled()) {
                    log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);
                }
            }
        }
        return securityManager;
    }

這個函式首先從該SubjectContext裡取出SecurityManager,如果沒有,就呼叫SecurityUtils的getSecurityManager從全域性中取。顯然這個函式再第一次呼叫的時候是找不到SecurityManager的,因此要返回到ensureSecurityManager進行設定。這樣,執行完ensureSecurityManager後,SecurityManager和SubjectContext就關聯起來了。

resolveSession

resolveSession定義在DefaultSecurityManager中,

    protected SubjectContext resolveSession(SubjectContext context) {
        if (context.resolveSession() != null) {
            log.debug("Context already contains a session.  Returning.");
            return context;
        }
        try {
            Session session = resolveContextSession(context);
            if (session != null) {
                context.setSession(session);
            }
        } catch (InvalidSessionException e) {
            log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +
                    "(session-less) Subject instance.", e);
        }
        return context;
    }

這個函式和上面的ensureSecurityManager類似,首先呼叫resolveSession
resolveSession定義在DefaultSubjectContext中,

    public Session resolveSession() {
        Session session = getSession();
        if (session == null) {
            Subject existingSubject = getSubject();
            if (existingSubject != null) {
                session = existingSubject.getSession(false);
            }
        }
        return session;
    }

這個函式首先從該SubjectContext中獲取Session,如果沒有,就獲取Subject,再從Subject中獲取Session,如果找到則返回,否則返回null。因為是第一次呼叫該函式,所以肯定為null,所以回到resolveSession函式中,接下來進入resolveContextSession函式,構造一個Session,並和該SubjectContext相關聯。
resolveContextSession定義在DefaultSecurityManager中,

    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
        SessionKey key = getSessionKey(context);
        if (key != null) {
            return getSession(key);
        }
        return null;
    }
    protected SessionKey getSessionKey(SubjectContext context) {
        Serializable sessionId = context.getSessionId();
        if (sessionId != null) {
            return new DefaultSessionKey(sessionId);
        }
        return null;
    }

這裡首先獲取sessionId ,這裡第一次呼叫也為null,因此直接返回null。因此resolveSession啥也沒幹。

resolvePrincipals

resolvePrincipals定義在DefaultSecurityManager中,

    protected SubjectContext resolvePrincipals(SubjectContext context) {

        PrincipalCollection principals = context.resolvePrincipals();

        if (CollectionUtils.isEmpty(principals)) {
            log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");

            principals = getRememberedIdentity(context);

            if (!CollectionUtils.isEmpty(principals)) {
                log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                        "for subject construction by the SubjectFactory.");

                context.setPrincipals(principals);
            } else {
                log.trace("No remembered identity found.  Returning original context.");
            }
        }

        return context;
    }

首先通過resolvePrincipals嘗試獲取PrincipalCollection,resolvePrincipals定義在DefaultSubjectContext中,

    public PrincipalCollection resolvePrincipals() {
        PrincipalCollection principals = getPrincipals();

        if (CollectionUtils.isEmpty(principals)) {
            AuthenticationInfo info = getAuthenticationInfo();
            if (info != null) {
                principals = info.getPrincipals();
            }
        }

        if (CollectionUtils.isEmpty(principals)) {
            Subject subject = getSubject();
            if (subject != null) {
                principals = subject.getPrincipals();
            }
        }

        if (CollectionUtils.isEmpty(principals)) {
            Session session = resolveSession();
            if (session != null) {
                principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
            }
        }

        return principals;
    }

該程式碼依次嘗試從SubjectContext、AuthenticationInfo、Subject和Session中獲取PrincipalCollection。這裡顯然為null,因此返回到resolvePrincipals中,通過getRememberedIdentity獲取。

getRememberedIdentity定義在DefaultSecurityManager中,

    protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
        RememberMeManager rmm = getRememberMeManager();
        if (rmm != null) {
            try {
                return rmm.getRememberedPrincipals(subjectContext);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                            "] threw an exception during getRememberedPrincipals().";
                    log.warn(msg, e);
                }
            }
        }
        return null;
    }

getRememberMeManager首先從DefaultWebSecurityManager中獲取RememberMeManager,在前面分析的DefaultWebSecurityManager建構函式中可知,getRememberMeManager將會返回CookieRememberMeManager,因此下面看getRememberedPrincipals函式,getRememberedPrincipals定義在CookieRememberMeManager的父類AbstractRememberMeManager中,

    public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principals = null;
        try {
            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
            if (bytes != null && bytes.length > 0) {
                principals = convertBytesToPrincipals(bytes, subjectContext);
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }

        return principals;
    }

getRememberedSerializedIdentity定義在CookieRememberMeManager中,

    protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {

        if (!WebUtils.isHttp(subjectContext)) {
            if (log.isDebugEnabled()) {
                String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +
                        "servlet request and response in order to retrieve the rememberMe cookie. Returning " +
                        "immediately and ignoring rememberMe operation.";
                log.debug(msg);
            }
            return null;
        }

        WebSubjectContext wsc = (WebSubjectContext) subjectContext;
        if (isIdentityRemoved(wsc)) {
            return null;
        }

        HttpServletRequest request = WebUtils.getHttpRequest(wsc);
        HttpServletResponse response = WebUtils.getHttpResponse(wsc);

        String base64 = getCookie().readValue(request, response);
        if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;

        if (base64 != null) {
            base64 = ensurePadding(base64);
            if (log.isTraceEnabled()) {
                log.trace("Acquired Base64 encoded identity [" + base64 + "]");
            }
            byte[] decoded = Base64.decode(base64);
            if (log.isTraceEnabled()) {
                log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
            }
            return decoded;
        } else {
            return null;
        }
    }

這裡傳入的引數並不是WebSubjectContext,因此會在第一個if內直接返回。因此層層往上返回到resolvePrincipals,最後啥也沒做。

doCreateSubject

    protected Subject doCreateSubject(SubjectContext context) {
        return getSubjectFactory().createSubject(context);
    }

getSubjectFactory()返回前面構造的DefaultWebSubjectFactory,因此createSubject定義在DefaultWebSubjectFactory中如下

    public Subject createSubject(SubjectContext context) {
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        }
        WebSubjectContext wsc = (WebSubjectContext) context;
        SecurityManager securityManager = wsc.resolveSecurityManager();
        Session session = wsc.resolveSession();
        boolean sessionEnabled = wsc.isSessionCreationEnabled();
        PrincipalCollection principals = wsc.resolvePrincipals();
        boolean authenticated = wsc.resolveAuthenticated();
        String host = wsc.resolveHost();
        ServletRequest request = wsc.resolveServletRequest();
        ServletResponse response = wsc.resolveServletResponse();

        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
                request, response, securityManager);
    }

因為這時context並不是WebSubjectContext,因此直接呼叫父類DefaultSubjectFactory的createSubject函式,

    public Subject createSubject(SubjectContext context) {
        SecurityManager securityManager = context.resolveSecurityManager();
        Session session = context.resolveSession();
        boolean sessionCreationEnabled = context.isSessionCreationEnabled();
        PrincipalCollection principals = context.resolvePrincipals();
        boolean authenticated = context.resolveAuthenticated();
        String host = context.resolveHost();

        return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
    }

這裡大部分引數都為null,然後直接呼叫了DelegatingSubject的建構函式。因此,回到doCreateSubject函式,這裡就是構造了一個DelegatingSubject函式。

save

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

這裡的subjectDAO是在前面建構函式中構造的DefaultSubjectDAO,因此看DefaultSubjectDAO的save函式,

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

isSessionStorageEnabled也定義在DefaultSubjectDAO中,

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

getSessionStorageEvaluator返回在DefaultSubjectDAO的建構函式中構造的DefaultSessionStorageEvaluator,因此接著看它的isSessionStorageEnabled函式,

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

這裡isSessionStorageEnabled預設的返回true,因此回到save中,接著看saveToSession,

    protected void saveToSession(Subject subject) {
        mergePrincipals(subject);
        mergeAuthenticationState(subject);
    }

該函式就是將Subject的狀態存在Session中,首先看mergePrincipals函式,

    protected void mergePrincipals(Subject subject) {

        PrincipalCollection currentPrincipals = null;

        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 (!CollectionUtils.isEmpty(currentPrincipals)) {
                session = subject.getSession();
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
            }
        } else {
            PrincipalCollection existingPrincipals =
                    (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

            if (CollectionUtils.isEmpty(currentPrincipals)) {
                if (!CollectionUtils.isEmpty(existingPrincipals)) {
                    session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                }
            } else {
                if (!currentPrincipals.equals(existingPrincipals)) {
                    session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
                }
            }
        }
    }

首先,在前面doCreateSubject中構造的確實是一個DelegatingSubject,這裡isRunAs會返回false,至於為什麼,這裡就不分析了。下面會進入第二個if,currentPrincipals會被賦值為null(檢視之前構造的DelegatingSubject)。下面看Subject的getSession函式,

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

這裡傳入的引數create為false,因此不會構造session,因此返回null。本章最後假設create為true,分析如何構造該Session。返回到mergePrincipals函式,因為session和currentPrincipals都為null,因此直接返回。所以第一次進入mergePrincipals函式,什麼都沒做。
回到saveToSession函式,再看mergeAuthenticationState函式,

    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);
            }
        } 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);
                }
            } else {
                if (existingAuthc != null) {
                    session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
                }
            }
        }
    }

和前面的分析類似,這裡也什麼也沒做。

bind

回到最前頭看SecurityUtils的getSubject函式,最後呼叫了ThreadContext的bind函式,

    public static void bind(Subject subject) {
        if (subject != null) {
            put(SUBJECT_KEY, subject);
        }
    }

看一下put,定義在ThreadContext中,

    public static void put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        if (value == null) {
            remove(key);
            return;
        }

        ensureResourcesInitialized();
        resources.get().put(key, value);

        if (log.isTraceEnabled()) {
            String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
    }

ensureResourcesInitialized就是初始化resources了,

    private static void ensureResourcesInitialized(){
        if (resources.get() == null){
           resources.set(new HashMap<Object, Object>());
        }
    }

因此最後就是簡單地將剛剛構造的Subject(其實是DelegatingSubject)放入resources中。