繼續上一篇文章的案例,第一次使用SecurityUtils.getSubject()來(lái)獲取Subject時(shí)
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }使用ThreadLocal模式來(lái)獲取,若沒(méi)有則創(chuàng )建一個(gè)并綁定到當前線(xiàn)程。此時(shí)創(chuàng )建使用的是Subject內部類(lèi)Builder來(lái)創(chuàng )建的,Builder會(huì )創(chuàng )建一個(gè)SubjectContext接口的實(shí)例DefaultSubjectContext,最終會(huì )委托securityManager來(lái)根據SubjectContext信息來(lái)創(chuàng )建一個(gè)Subject,下面詳細說(shuō)下該過(guò)程,在DefaultSecurityManager的createSubject方法中:
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; }首先就是復制SubjectContext,SubjectContext 接口繼承了Map<String, Object>,然后加入了幾個(gè)重要的SecurityManager、SessionId、Subject、PrincipalCollection、Session、boolean authenticated、boolean sessionCreationEnabled、Host、AuthenticationToken、AuthenticationInfo等眾多信息。
然后來(lái)討論下接口設計:
討論1:首先是SubjectContext為什么要去實(shí)現Map<String, Object>?
SubjectContext提供了常用的get、set方法,還提供了一個(gè)resolve方法,以SecurityManager為例:
SecurityManager getSecurityManager(); void setSecurityManager(SecurityManager securityManager); SecurityManager resolveSecurityManager();
這些get、set方法則用于常用的設置和獲取,而resolve則表示先調用getSecurityManager,如果獲取不到,則使用其他途徑來(lái)獲取,如DefaultSubjectContext的實(shí)現:
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; }如果getSecurityManager獲取不到,則使用SecurityUtils工具來(lái)獲取。
再如resolvePrincipals
public PrincipalCollection resolvePrincipals() { PrincipalCollection principals = getPrincipals(); if (CollectionUtils.isEmpty(principals)) { //check to see if they were just authenticated: 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)) { //try the session: Session session = resolveSession(); if (session != null) { principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY); } } return principals; }普通的getPrincipals()獲取不到,嘗試使用其他屬性來(lái)獲取。
討論2:此時(shí)就有一個(gè)問(wèn)題,有必要再對外公開(kāi)getPrincipals方法嗎?什么情況下外界會(huì )去調用getPrincipals方法而不會(huì )去調用resolvePrincipals方法?
然后我們繼續回到上面的類(lèi)圖設計上:
DefaultSubjectContext繼承了MapContext,MapContext又實(shí)現了Map<String, Object>,看下此時(shí)的MapContext有什么東西:
public class MapContext implements Map<String, Object>, Serializable { private static final long serialVersionUID = 5373399119017820322L; private final Map<String, Object> backingMap; public MapContext() { this.backingMap = new HashMap<String, Object>(); } public MapContext(Map<String, Object> map) { this(); if (!CollectionUtils.isEmpty(map)) { this.backingMap.putAll(map); } } //略}MapContext內部擁有一個(gè)類(lèi)型為HashMap的backingMap屬性,大部分方法都由HashMap來(lái)實(shí)現,然后僅僅更改某些行為,MapContext沒(méi)有選擇去繼承HashMap,而是使用了組合的方式,更加容易去擴展,如backingMap的類(lèi)型不一定非要選擇HashMap,可以換成其他的Map實(shí)現,一旦MapContext選擇繼承HashMap,如果想對其他的Map類(lèi)型進(jìn)行同樣的功能增強的話(huà),就需要另寫(xiě)一個(gè)類(lèi)來(lái)繼承它然后改變一些方法實(shí)現,這樣的話(huà)就會(huì )有很多重復代碼。這也是設計模式所強調的少用繼承多用組合。但是MapContext的寫(xiě)法使得子類(lèi)沒(méi)法去替換HashMap,哎,心塞 。
MapContext又提供了如下幾個(gè)返回值不可修改的方法:
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()); }有點(diǎn)扯遠了。繼續回到DefaultSecurityManager創(chuàng )建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; }對于context,把能獲取到的參數都湊齊,SecurityManager、Session。resolveSession嘗試獲取context的map中獲取Session,若沒(méi)有則嘗試獲取context的map中的Subject,如果存在的話(huà),根據此Subject來(lái)獲取Session,若沒(méi)有再?lài)L試獲取sessionId,若果有了sessionId則構建成一個(gè)DefaultSessionKey來(lái)獲取對應的Session。
整個(gè)過(guò)程如下;
protected SubjectContext resolveSession(SubjectContext context) { if (context.resolveSession() != null) { log.debug("Context already contains a session. Returning."); return context; } try { //Context couldn't resolve it directly, let's see if we can since we have direct access to //the session manager: 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; }先看下context.resolveSession():
public Session resolveSession() { //這里則是直接從map中取出Session Session session = getSession(); if (session == null) { //try the Subject if it exists: //若果沒(méi)有,嘗試從map中取出Subject Subject existingSubject = getSubject(); if (existingSubject != null) { //這里就是Subject獲取session的方法,需要詳細看下 session = existingSubject.getSession(false); } } return session; }existingSubject.getSession(false):通過(guò)Subject獲取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); this.session = decorate(session); } return this.session; }getSession()的參數表示是否創(chuàng )建session,如果Session為空,并且傳遞的參數為true,則會(huì )創(chuàng )建一個(gè)Session。然而這里傳遞的是false,也就是說(shuō)不會(huì )在創(chuàng )建Subject的時(shí)候來(lái)創(chuàng )建Session,所以把創(chuàng )建Session過(guò)程說(shuō)完后,再回到此處是要記著(zhù)不會(huì )去創(chuàng )建一個(gè)Session。但是我們可以來(lái)看下是如何創(chuàng )建Session的,整體三大步驟,先創(chuàng )建一個(gè)SessionContext ,然后根據SessionContext 來(lái)創(chuàng )建Session,最后是裝飾Session,由于創(chuàng )建Session過(guò)程內容比較多,先說(shuō)說(shuō)裝飾Session。
protected Session decorate(Session session) { if (session == null) { throw new IllegalArgumentException("session cannot be null"); } return new StoppingAwareProxiedSession(session, this); }裝飾Session就是講Session和DelegatingSubject封裝起來(lái)。
然后來(lái)說(shuō)Session的創(chuàng )建過(guò)程,這和Subject的創(chuàng )建方式差不多。
同樣是SessionContext的接口設計:

和SubjectContext相當雷同。
看下SessionContext的主要內容:
void setHost(String host); String getHost(); Serializable getSessionId(); void setSessionId(Serializable sessionId);
主要兩個(gè)內容,host和sessionId。
接下來(lái)看下如何由SessionContext來(lái)創(chuàng )建Session:
protected Session doCreateSession(SessionContext context) { Session s = newSessionInstance(context); if (log.isTraceEnabled()) { log.trace("Creating session for host {}", s.getHost()); } create(s); return s; } protected Session newSessionInstance(SessionContext context) { return getSessionFactory().createSession(context); }和Subject一樣也是由一個(gè)SessionFactory根據SessionContext來(lái)創(chuàng )建出一個(gè)Session,看下默認的SessionFactory SimpleSessionFactory的創(chuàng )建過(guò)程:
public Session createSession(SessionContext initData) { if (initData != null) { String host = initData.getHost(); if (host != null) { return new SimpleSession(host); } } return new SimpleSession(); }如果SessionContext有host信息,就傳遞給Session,然后就是直接new一個(gè)Session接口的實(shí)現SimpleSession,先看下Session接口有哪些內容:
public interface Session { Serializable getId(); Date getStartTimestamp(); Date getLastAccessTime(); long getTimeout() throws InvalidSessionException; void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException; String getHost(); void touch() throws InvalidSessionException; void stop() throws InvalidSessionException; Collection<Object> getAttributeKeys() throws InvalidSessionException; Object getAttribute(Object key) throws InvalidSessionException; void setAttribute(Object key, Object value) throws InvalidSessionException; Object removeAttribute(Object key) throws InvalidSessionException;}id:Session的唯一標識,創(chuàng )建時(shí)間、超時(shí)時(shí)間等內容。
再看SimpleSession的創(chuàng )建過(guò)程:
public SimpleSession() { this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; this.startTimestamp = new Date(); this.lastAccessTime = this.startTimestamp; } public SimpleSession(String host) { this(); this.host = host; }設置下超時(shí)時(shí)間為DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT 30分鐘,startTimestamp 和lastAccessTime設置為現在開(kāi)始。就這樣構建出了一個(gè)Session的實(shí)例,然后就是需要將該實(shí)例保存起來(lái):
protected Session doCreateSession(SessionContext context) { Session s = newSessionInstance(context); if (log.isTraceEnabled()) { log.trace("Creating session for host {}", s.getHost()); } create(s); return s; }protected void create(Session session) { if (log.isDebugEnabled()) { log.debug("Creating new EIS record for new session instance [" + session + "]"); } sessionDAO.create(session); }即該進(jìn)行create(s)操作了,又和Subject極度的相像,使用sessionDAO來(lái)保存剛才創(chuàng )建的Session。再來(lái)看下SessionDAO接口:
public interface SessionDAO { Serializable create(Session session); Session readSession(Serializable sessionId) throws UnknownSessionException; void update(Session session) throws UnknownSessionException; void delete(Session session); Collection<Session> getActiveSessions();}也就是對所有的Session進(jìn)行增刪該查,SessionDAO 接口繼承關(guān)系如下:

AbstractSessionDAO:有一個(gè)重要的屬性SessionIdGenerator,它負責給Session創(chuàng )建sessionId,SessionIdGenerator接口如下:
public interface SessionIdGenerator { Serializable generateId(Session session);}很簡(jiǎn)單,參數為Session,返回sessionId。SessionIdGenerator 的實(shí)現有兩個(gè)JavaUuidSessionIdGenerator、RandomSessionIdGenerator。而AbstractSessionDAO默認采用的是JavaUuidSessionIdGenerator,如下:
public AbstractSessionDAO() { this.sessionIdGenerator = new JavaUuidSessionIdGenerator(); }MemorySessionDAO繼承了AbstractSessionDAO,它把Session存儲在一個(gè)ConcurrentMap<Serializable, Session> sessions集合中,key為sessionId,value為Session。
CachingSessionDAO:主要配合在別的地方存儲session。先不介紹,之后的文章再詳細說(shuō)。
對于本案例來(lái)說(shuō)SessionDAO為MemorySessionDAO。至此整個(gè)Session的創(chuàng )建過(guò)程就走通了。
剛才雖然說(shuō)了整個(gè)Session的創(chuàng )建過(guò)程,回到上文所說(shuō)的,不會(huì )去創(chuàng )建Session的地方。在創(chuàng )建Subject搜集session信息時(shí),使用的此時(shí)的Subject的Session、sessionId都為空,所以獲取不到Session。然后就是doCreateSubject:
protected Subject doCreateSubject(SubjectContext context) { return getSubjectFactory().createSubject(context); }就是通過(guò)SubjectFactory工廠(chǎng)接口來(lái)創(chuàng )建Subject的,而DefaultSecurityManager默認使用的
SubjectFactory是DefaultSubjectFactory:
public DefaultSecurityManager() { super(); this.subjectFactory = new DefaultSubjectFactory(); this.subjectDAO = new DefaultSubjectDAO(); }繼續看DefaultSubjectFactory是怎么創(chuàng )建Subject的:
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); }仍然就是將這些屬性傳遞給DelegatingSubject,也沒(méi)什么好說(shuō)的。創(chuàng )建完成之后,就需要將剛創(chuàng )建的Subject保存起來(lái),仍回到:
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; }來(lái)看下save方法:
protected void save(Subject subject) { this.subjectDAO.save(subject); }可以看到又是使用另一個(gè)模塊來(lái)完成的即SubjectDAO,SubjectDAO接口如下:
public interface SubjectDAO { Subject save(Subject subject); void delete(Subject subject);}很簡(jiǎn)單,就是保存和刪除一個(gè)Subject。我們看下具體的實(shí)現類(lèi)DefaultSubjectDAO是如何來(lái)保存的:
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,是否要存儲該Subject的session來(lái)
DefaultSubjectDAO:有一個(gè)重要屬性SessionStorageEvaluator,它是用來(lái)決定一個(gè)Subject的Session來(lái)記錄Subject的狀態(tài),接口如下
public interface SessionStorageEvaluator { boolean isSessionStorageEnabled(Subject subject);}其實(shí)現為DefaultSessionStorageEvaluator:
public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator { private boolean sessionStorageEnabled = true; public boolean isSessionStorageEnabled(Subject subject) { return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled(); }決定策略就是通過(guò)DefaultSessionStorageEvaluator 的sessionStorageEnabled的true或false 和subject是否有Session對象來(lái)決定的。如果允許存儲Subject的Session的話(huà),下面就說(shuō)具體的存儲過(guò)程:
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) { //只有當Session為空,并且currentPrincipals不為空的時(shí)候才會(huì )去創(chuàng )建Session //Subject subject = SecurityUtils.getSubject()此時(shí)兩者都是為空的, //不會(huì )去創(chuàng )建Session if (!CollectionUtils.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 (CollectionUtils.isEmpty(currentPrincipals)) { if (!CollectionUtils.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 } } }上面有我們關(guān)心的重點(diǎn),當subject.getSession(false)獲取的Session為空時(shí)(它不會(huì )去創(chuàng )建Session),此時(shí)就需要去創(chuàng )建Session,subject.getSession()則默認調用的是subject.getSession(true),則會(huì )進(jìn)行Session的創(chuàng )建,創(chuàng )建過(guò)程上文已詳細說(shuō)明了。
在第一次創(chuàng )建Subject的時(shí)候
Subject subject = SecurityUtils.getSubject();
雖然Session為空,但此時(shí)還沒(méi)有用戶(hù)身份信息,也不會(huì )去創(chuàng )建Session。案例中的subject.login(token),該過(guò)程則會(huì )去創(chuàng )建Session,具體看下過(guò)程:
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 } //在該過(guò)程會(huì )進(jìn)行Session的創(chuàng )建 Subject loggedIn = createSubject(token, info, subject); onSuccessfulLogin(token, info, loggedIn); return loggedIn; }對于驗證過(guò)程上篇文章已經(jīng)簡(jiǎn)單說(shuō)明了,這里不再說(shuō)明,重點(diǎn)還是在驗證通過(guò)后,會(huì )設置Subject的身份,即用戶(hù)名:
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); }有了認證成功的AuthenticationInfo信息,SubjectContext在resolvePrincipals便可以獲取用戶(hù)信息,即通過(guò)AuthenticationInfo的getPrincipals()來(lái)獲得。
public PrincipalCollection resolvePrincipals() { PrincipalCollection principals = getPrincipals(); if (CollectionUtils.isEmpty(principals)) { //check to see if they were just authenticated: 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)) { //try the session: Session session = resolveSession(); if (session != null) { principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY); } } return principals; }PrincipalCollection不為空了,在save(subject)的時(shí)候會(huì )得到session為空,同時(shí)PrincipalCollection不為空,則會(huì )執行Session的創(chuàng )建。也就是說(shuō)在認證通過(guò)后,會(huì )執行Session的創(chuàng )建,Session創(chuàng )建完成之后會(huì )進(jìn)行一次裝飾,即用StoppingAwareProxiedSession將創(chuàng )建出來(lái)的session和subject關(guān)聯(lián)起來(lái),然后又進(jìn)行如下操作:
public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); //這里的Subject則是經(jīng)過(guò)認證后創(chuàng )建的并且也含有剛才創(chuàng )建的session,類(lèi)型為 //StoppingAwareProxiedSession,即是該subject本身和session的合體。 Subject subject = securityManager.login(this, token); PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { //在這里可以看到又進(jìn)行了一次裝飾 this.session = decorate(session); } else { this.session = null; } }subject 創(chuàng )建出來(lái)之后,暫且叫內部subject,就是把認證通過(guò)的內部subject的信息和session復制給我們外界使用的subject.login(token)的subject中,這個(gè)subject暫且叫外部subject,看下session的賦值,又進(jìn)行了一次裝飾,這次裝飾則把session(類(lèi)型為StoppingAwareProxiedSession,即是內部subject和session的合體)和外部subject綁定到一起。
最后來(lái)總結下,首先是Subject和Session的接口類(lèi)圖:

然后就是Subject subject = SecurityUtils.getSubject()的一個(gè)簡(jiǎn)易的流程圖:
最后是subject.login(token)的簡(jiǎn)易流程圖:

作者:乒乓狂魔
聯(lián)系客服