Subject反正就好像呈現的視圖。全部Subject 都綁定到SecurityManager,與Subject的全部交互都會委託給SecurityManager;能夠把Subject認爲是一個門面;SecurityManager纔是實際的執行者;
對於上面這句話的理解呢?怎麼去理解這個很重要,看看別人的代碼設計的流程也是比較的清楚的,Subject都綁定到了SecurityManager,所以咱們在建立Subject的時候,必須給框架的內部綁定了一個SecurityManager,在前一個博客,咱們已經基本的看了SecurityManager,大體的主要的架構,如今來看看Subject的主要的源碼,學習一下別人這麼寫的用意何在?本身也是多多的總結頗有很好,看看別人的優秀代碼。
和上一個同樣的
shrio.iniandroid
[users] zhang=123 wang=123
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //二、獲得SecurityManager實例 並綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //三、獲得Subject及建立用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { //四、登陸,即身份驗證 subject.login(token); } catch (AuthenticationException e) { //五、身份驗證失敗 } Assert.assertEquals(true, subject.isAuthenticated()); //斷言用戶已經登陸 //六、退出 subject.logout();
SecurityUtils:是一個很是關鍵的類,這裏能夠獲取到咱們的全局的資源,和當前的線程相關的,放置在ThreadLocal裏面的,Subject也是如此哦,和當前的環境相關apache
package org.apache.shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; /** * Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment. *獲取Subject,和當前的環境相關 * @since 0.2 */ public abstract class SecurityUtils { /** *ThreadContext 這裏保存的是和線程相關的東西,這裏只是個備份 *感受做用不是很大,這裏只是用做在單線程的環境中 * ONLY used as a 'backup' in VM Singleton environments (that is, standalone environments), since the * ThreadContext should always be the primary source for Subject instances when possible. */ private static SecurityManager securityManager; public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; } //這裏通常都是隻在單線程中使用的, //獲取這個通常在ThreadLoacal中獲取,而不是這裏哦 public static void setSecurityManager(SecurityManager securityManager) { SecurityUtils.securityManager = securityManager; } //每次都是先去找線程相關的,而後沒有在去在備份的static public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException { SecurityManager securityManager = ThreadContext.getSecurityManager(); if (securityManager == null) { securityManager = SecurityUtils.securityManager; } if (securityManager == null) { throw new UnavailableSecurityManagerException(msg); } return securityManager; } }
如咱們所知道的,設置securityManager,以後才能綁定到.子進程共享父進程的信息 ThreadLoacl http://blog.csdn.net/jiafu1115/article/details/7548605 這裏講的還不錯。http://blog.csdn.net/feier7501/article/details/19088905 這裏的例子 筆者也去試了一會兒,這種用法過高級了。設計模式
public abstract class ThreadContext { // 這種惟一的Key設置值得學習一下哦,經過名字 public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY"; public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY"; //這裏使用了InheritableThreadLocalMap //子線程會接收全部可繼承的線程局部變量的初始值, //以得到父線程所具備的值。一般,子線程的值與父線程的值是一致的 //這個就是比較高級的用法了,讓子線程也能夠獲取到 private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>(); protected ThreadContext() { } //這個每次獲取的都是新的哦,線程安全的。 public static Map<Object, Object> getResources() { return resources != null ? new HashMap<Object, Object>(resources.get()) : null; } private static Object getValue(Object key) { return resources.get().get(key); } public static Object get(Object key) { Object value = getValue(key); return value; } public static void put(Object key, Object value) { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } if (value == null) { remove(key); return; } resources.get().put(key, value); } public static Object remove(Object key) { Object value = resources.get().remove(key); return value; } public static void remove() { resources.remove(); } //獲取總管家 public static SecurityManager getSecurityManager() { return (SecurityManager) get(SECURITY_MANAGER_KEY); } public static void bind(SecurityManager securityManager) { if (securityManager != null) { put(SECURITY_MANAGER_KEY, securityManager); } } public static SecurityManager unbindSecurityManager() { return (SecurityManager) remove(SECURITY_MANAGER_KEY); } public static Subject getSubject() { return (Subject) get(SUBJECT_KEY); } public static void bind(Subject subject) { if (subject != null) { put(SUBJECT_KEY, subject); } } private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> { protected Map<Object, Object> initialValue() { return new HashMap<Object, Object>(); } /** * This implementation was added to address a * <a href="http://jsecurity.markmail.org/search/?q=#query:+page:1+mid:xqi2yxurwmrpqrvj+state:results"> * user-reported issue</a>. * @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method. * @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap). */ @SuppressWarnings({"unchecked"}) protected Map<Object, Object> childValue(Map<Object, Object> parentValue) { if (parentValue != null) { return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone(); } else { return null; } } } }
上面的當前線程的值,保存了總管家了,和Subject的信息。Subject和總管家之間的關係如何呢?這個看看建立Subject的時候怎麼去處理的。一步步的解開謎底。
以前已經綁定總管家了安全
//三、獲得Subject及建立用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject();
–>下一步從當前線程中獲取Subject有沒有?沒有建立一個,經過Subject本身的Build設計模式,建立一個Subject,此時咱們跟進Subject裏面去看看。public interface Subject,Subject是個接口,Builder是一個內部靜態類。這種用法你不會使用吧session
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }
Subject內部結構圖能夠看到Builder中和管家綁定有關係吧!並且這個接口有不少的權限的查看信息這個和管家裏面的繼承結構那有關係的,哈哈,代理的模式估計應該就是那樣的。這種定義build能夠值得學習,用起來比較爽,好比Okhttp好像也是這樣的,模式哦不少的默認的參數,也能夠本身設置本身喜歡的模式,進行處理。這個就是優勢,好比android裏面的Dialog的參數設置,你能夠本身設置,也可使用默認的參數。 架構
public static class Builder { /** * Hold all contextual data via the Builder instance's method invocations to be sent to the * {@code SecurityManager} during the {@link #buildSubject} call. 數據保持器,在最後調用buildSubject的時候被使用。 */ private final SubjectContext subjectContext; private final SecurityManager securityManager; /** * Constructs a new {@link Subject.Builder} instance, using the {@code SecurityManager} instance available */ //這裏使用了管家 SubjectContext 保存數據?被 // sent to the {@code SecurityManager} to create a new {@code Subject} instance. public Builder() { this(SecurityUtils.getSecurityManager()); } public Builder(SecurityManager securityManager) { if (securityManager == null) { throw new NullPointerException("null."); } this.securityManager = securityManager; this.subjectContext = newSubjectContextInstance(); if (this.subjectContext == null) { throw new IllegalStateException("newSubjectContextInstance' " + "cannot be null."); } //這個有點意思了,保存當前管家的一個引用。 this.subjectContext.setSecurityManager(securityManager); } /** * Creates a new {@code SubjectContext} instance to be used to populate with subject contextual data that * will then be sent to the {@code SecurityManager} to create a new {@code Subject} instance. * @return a new {@code SubjectContext} instance */ //這個有點意思,放置在管家中去建立一個Subject protected SubjectContext newSubjectContextInstance() { return new DefaultSubjectContext(); } //讓後代使用 protected SubjectContext getSubjectContext() { return this.subjectContext; } public Builder sessionId(Serializable sessionId) { if (sessionId != null) { this.subjectContext.setSessionId(sessionId); } return this; } public Builder host(String host) { if (StringUtils.hasText(host)) { this.subjectContext.setHost(host); } return this; } ...... //這裏纔是真正的返回實例,這裏調用了管家建立的方法 //SubjectContext 建立的信息,反應到當前的信息當中去處理 public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); } }
DefaultSubjectContext的結構又是如何的?
public class DefaultSubjectContext extends MapContext implements SubjectContext
DefaultSubjectContext 中的信息字段是由MapContext這個類型安全的來維護的,DefaultSubjectContext 中的全部的字段的信息都是放置在Map中的去維護的,且能夠指定返回類型的安全性,若是非法,觸發異常。MapContext中主要是維護DefaultSubjectContext 中定義的字段的信息。 框架
簡單介紹 DefaultSubjectContext 中的信息維護都是這樣的類型學習
//這樣能夠指定返回的類型哦,不對的話,觸發異常 public SecurityManager getSecurityManager() { return getTypedValue(SECURITY_MANAGER, SecurityManager.class); } //非空插入哦 public void setSecurityManager(SecurityManager securityManager) { nullSafePut(SECURITY_MANAGER, securityManager); }
MapContext設置得也是比較的精巧,獲取的成員變量backingMap 是不容許直接引用的哦ui
private final Map<String, Object> backingMap; public MapContext() { this.backingMap = new HashMap<String, Object>(); }
不讓外面直接的就引用,修改值。this
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()); }
非空檢查
protected void nullSafePut(String key, Object value) { if (value != null) { put(key, value); } }
檢查獲得的結果,是否是期待的呢?類型安全
isAssignableFrom()方法是從類繼承的角度去判斷,instanceof()方法是從實例繼承的角度去判斷。
isAssignableFrom()方法是判斷是否爲某個類的父類,instanceof()方法是判斷是否某個類的子類。
Class.isAssignableFrom()是用來判斷一個類Class1和另外一個類Class2是否相同或是另外一個類的子類或接口。
我記得好像是在Java神書上面說過的。
protected <E> E getTypedValue(String key, Class<E> type) { E found = null; Object o = backingMap.get(key); if (o != null) { if (!type.isAssignableFrom(o.getClass())) { String msg = "Invalid object found in SubjectContext「; throw new IllegalArgumentException(msg); } found = (E) o; } return found; }
說彪了,其實都是學習不要緊的…
繼續以前的Subject的內部類建立Subject的過程最後是
這個時候和咱們的管家扯上關係了,咱們知道管家的繼承結構很是的複雜,裏面的處理流程很是的多,最後的實現是在
public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); }
最後的一個管理者實現了創造subject的方法
DefaultSecurityManager,這裏作了一些亂七八糟的東西很難懂,跟着業務..
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), //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
protected SubjectFactory subjectFactory; public DefaultSecurityManager() { super(); this.subjectFactory = new DefaultSubjectFactory(); this.subjectDAO = new DefaultSubjectDAO(); } //調用的這裏哦 protected Subject doCreateSubject(SubjectContext context) { return getSubjectFactory().createSubject(context); }
DefaultSubjectFactory 惟一的實現了SubjectFactory
SubjectContext 這個運輸信息的,終於被弄出來了,而後呢,建立一個Subject的實現,這個是最終的目的。 DelegatingSubject 建立一個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); }
而後就是subjectDao保存,這個不在去看了…
可是subject.login->使用的是實現類DelegatingSubject 中的總管家的的方法,而後總管家在調用內部的實現。調用內部的驗證,在調用….這樣的關係就拉上了。
一、首先調用Subject.login(token)進行登陸,其會自動委託給Security Manager,調用以前必 須經過SecurityUtils. setSecurityManager()設置; 二、SecurityManager負責真正的身份驗證邏輯;它會委託給Authenticator進行身份驗證; 三、Authenticator纔是真正的身份驗證者,Shiro API中核心的身份認證入口點,此處能夠自 定義插入本身的實現; 四、Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份驗證,默認 ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證; 五、Authenticator 會把相應的token 傳入Realm,從Realm 獲取身份驗證信息,若是沒有返 回/拋出異常表示身份驗證失敗了。此處能夠配置多個Realm,將按照相應的順序及策略進 行訪問。 哈哈,這裏這麼多的東西,我還沒開始瞭解呢!————————————————版權聲明:本文爲CSDN博主「汪小哥」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/u012881904/article/details/53726407