Subject反正就好像呈現的視圖。所有Subject 都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;
對於上面這句話的理解呢?怎么去理解這個很重要,看看別人的代碼設計的流程也是比較的清楚的,Subject都綁定到了SecurityManager,因此我們在創建Subject的時候,必須給框架的內部綁定了一個SecurityManager,在前一個博客,我們已經基本的看了SecurityManager,大致的主要的架構,現在來看看Subject的主要的源碼,學習一下別人這么寫的用意何在?自己也是多多的總結很有很好,看看別人的優秀代碼。
和上一個一樣的
shrio.ini
[users] zhang=123 wang=123
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2、得到SecurityManager實例 並綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { //4、登錄,即身份驗證 subject.login(token); } catch (AuthenticationException e) { //5、身份驗證失敗 } Assert.assertEquals(true, subject.isAuthenticated()); //斷言用戶已經登錄 //6、退出 subject.logout();
SecurityUtils:是一個非常關鍵的類,這里可以獲取到我們的全局的資源,和當前的線程相關的,放置在ThreadLocal里面的,Subject也是如此哦,和當前的環境相關
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的時候怎么去處理的。一步步的解開謎底。
之前已經綁定總管家了
//3、得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject();
–>下一步從當前線程中獲取Subject有沒有?沒有創建一個,通過Subject自己的Build設計模式,創建一個Subject,此時我們跟進Subject里面去看看。public interface Subject,Subject是個接口,Builder是一個內部靜態類。這種用法你不會使用吧
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 是不允許直接引用的哦
private final Map<String, Object> backingMap; public MapContext() { this.backingMap = new HashMap<String, Object>(); }
不讓外面直接的就引用,修改值。
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 中的總管家的的方法,然后總管家在調用內部的實現。調用內部的驗證,在調用….這樣的關系就拉上了。
1、首先調用Subject.login(token)進行登錄,其會自動委托給Security Manager,調用之前必
須通過SecurityUtils. setSecurityManager()設置;
2、SecurityManager負責真正的身份驗證邏輯;它會委托給Authenticator進行身份驗證;
3、Authenticator才是真正的身份驗證者,Shiro API中核心的身份認證入口點,此處可以自
定義插入自己的實現;
4、Authenticator可能會委托給相應的AuthenticationStrategy進行多Realm身份驗證,默認
ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證;
5、Authenticator 會把相應的token 傳入Realm,從Realm 獲取身份驗證信息,如果沒有返
回/拋出異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進
行訪問。
哈哈,這里這么多的東西,我還沒開始了解呢!
————————————————
版權聲明:本文為CSDN博主「汪小哥」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012881904/article/details/53726407