Shiro框架 Subject、SecurityManager、線程之間的關係

Subject與SecurityManager之間的關係

Subject對象的創建

Subject的創建是在過濾器中進行創建的,之後我會出過濾器相關的文章

try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

createSubject(request, response); 中會根據request和response參數來進行創建。

創建的方式是採用構造者模式

public static class Builder extends Subject.Builder {

        public Builder(ServletRequest request, ServletResponse response) {
            this(SecurityUtils.getSecurityManager(), request, response);
        }
        public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
            super(securityManager);
            if (request == null) {
                throw new IllegalArgumentException("ServletRequest argument cannot be null.");
            }
            if (response == null) {
                throw new IllegalArgumentException("ServletResponse argument cannot be null.");
            }
            setRequest(request);
            setResponse(response);
        }
        @Override
        protected SubjectContext newSubjectContextInstance() {
            return new DefaultWebSubjectContext();
        }
        protected Builder setRequest(ServletRequest request) {
            if (request != null) {
                ((WebSubjectContext) getSubjectContext()).setServletRequest(request);
            }
            return this;
        }
        protected Builder setResponse(ServletResponse response) {
            if (response != null) {
                ((WebSubjectContext) getSubjectContext()).setServletResponse(response);
            }
            return this;
        }
        public WebSubject buildWebSubject() {
            Subject subject = super.buildSubject();
            if (!(subject instanceof WebSubject)) {
                String msg = "Subject implementation returned from the SecurityManager was not a " +
                        WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager " +
                        "has been configured and made available to this builder.";
                throw new IllegalStateException(msg);
            }
            return (WebSubject) subject;
        }
    }

在Builder的構造方法中,調用父類構造方法,父類也叫Builder

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

主要完成的事情有:

  1. 生成DefaultWebSubjectContext對象,這個對象從類的結構上來看大致是一個實現了Map的功能
    在這裏插入圖片描述MapContext對象中使用了backingMap變量
    當作爲整個對象返回時,採用不可變方式返回,使原有數據不受到污染
public Set<String> keySet() {
        return Collections.unmodifiableSet(backingMap.keySet());
    }

    public Collection<Object> values() {
        return Collections.unmodifiableCollection(backingMap.values());
    }

    public Set<Entry<String, Object>> entrySet() {
        return Collections.unmodifiableSet(backingMap.entrySet());
    }
  1. securityManager設置進入DefaultWebSubjectContext對象

最後在在類的Builder構造方法中,將requestresponse也設置進DefaultWebSubjectContext對象中
最後調用buildWebSubject方法來進行Subject的創建

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

創建的過程如下,裏面是一些業務上的代碼,博主還沒有啃透徹,等我把大致流程弄懂,回來再細細研究

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

重點在這行Subject subject = doCreateSubject(context);來創建Subject對象

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

getSubjectFactory()中獲取工廠對象,該工廠對象爲我們配置文件中配置的CasSubjectFactory
在這裏插入圖片描述最後會調用父類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);
    }

securityManager對象會作爲WebDelegatingSubject變量存儲,Subject真正的實現是WebDelegatingSubject
當我們調用Subject.login,會執行到WebDelegatingSubject父類DelegatingSubject中,而securityManager會通過構造函數,傳遞到父類屬性中
在這裏插入圖片描述

Subject、SecurityManager與線程之間的關係

當通過Shiro進行登錄驗證時,需要獲取Subject對象,而在同一個線程中,每次通過SecurityUtils都是同一個對象。

Subject生成完之後談到的問題是如何綁定到線程中

在如下代碼中執行的綁定

subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
public <V> V execute(Callable<V> callable) throws ExecutionException {
        Callable<V> associated = associateWith(callable);
        try {
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }
public <V> Callable<V> associateWith(Callable<V> callable) {
        return new SubjectCallable<V>(this, callable);
    }

生成了SubjectCallable對象,改類只是實現了Callable接口,沒什麼好說的

public SubjectCallable(Subject subject, Callable<V> delegate) {
        this(new SubjectThreadState(subject), delegate);
    }

最最最後生成SubjectThreadState對象,這個對象很重要,主要負責將subject和securityManager通過ThreadContext對象綁定到當前線程中

當外層調用call方法時
在這裏插入圖片描述會調用SubjectThreadState的bind方法

在這裏插入圖片描述實際綁定時會調用ThreadContext的bind方法

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

最最最重要的是

private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

這個實現,能夠保證在子線程中與主線程共享一個對象,可以參見我之後的文章,也會對這個類進行分析的

到此爲止Subject與SecurityManager綁定與Subject與線程的綁定就講完了

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