Shiro quickstart source code analysis(一)

Shiro sample中的源碼分析:ios

  1. Factory.getInstance(); -> 指定具體的Factory類型:IniSecurityManagerFactory.getInstance();
  2. 根據以前初始化的Ini實例,建立SecurityManager: IniSecurityManagerFactory.createInstance(Ini ini); -> IniSecurityManagerFactory.createSecurityManager(Ini ini);
  3. 剖析具體建立SecurityManager實例的過程: createSecurityManager(Ini ini);
    1. 建立默認的SecurityManager實例:new DefaultSecurityManager();
    2. 建立Realm實例: new IniRealm(); 並將Ini 賦值給IniRealm實例: iniRealm.setIni(ini);
    3. 獲得2個實例: securityManager, iniRealm, 把他們裝入map<String, ?> 獲得defauls對象 ??? 爲何須要buildInstances()? 第三步顯然已經獲得SecurityManager實例了
    4. 調用方法 buildInstances(Ini.Section mainSection, Map<String, ?> defaults);[note: mainSection = null, defaults = 第三步獲得的map對象] , 剖析buildInstances(Ini.Section mainSection, Map<String, ?> defaults);
      1. 初始化builder 對象: reflectionBuilder = new ReflectionBuilder(defaults); -> this.objects = createDefaultObjectMap(); [note: 添加EventBus: {map.put(EVENT_BUS_NAME, new DefaultEventBus()); -> return map;}]
      2. apply(defaults); -> reflectionBuilder.objects.putAll(defaults);[note: 將securityManager, realm, eventBus放到map裏]
      3. 檢驗defaults中的value 是否instanceof(EventBus); -> [Yes -> setEventBus(reflectionBuilder.eventBus), No -> 判斷是否instanceof(EventSubscriber); -> [Yes -> eventBus.register(defaults.getKey("xxx")); registeredEventSubscribers.put(defaults.getEntrySet().getEntry().getKey(), defaults.getEntrySet().getEntry().getValue);]]
      4. builder.buildObjects(section); -> 若是reflectionBuilder.objects的每個都instanceof Initializable 對象則執行initializable.init();
      5. 返回通過上述4個步驟處理的objects[note: securityManager, realm, eventBus]
    5. 檢測SecurityManager中是否存在Collection<Realm>:isAutoApplyRealms(SecurityManager securityManager); -> [Yes -> return securityManager, No -> 經過遍歷defaults獲得 Realm 實例; -> securityManager.setRealms(Collection<Realm> realms);]
    6. return securityManager;
  4. SecurityUtils.setSecurityManager(secMger);[note: secMger from step 3.6]
  5. SecurityUtils.getSubject(); -> 檢查線程綁定的Subject 對象是否存在:[Yes -> return subject, No -> Subject subject = (new Subject.Builder()).buildSubject(); threadContext.bind(subject); return subject;], !!! 重點剖析 (new Subject.Builder()).buildSubject();
    1. new Subject.Builder(); -> new Subject.Builder(SecurityManager securityManager); -> 首先建立Subject的內部類Builder對象 -> 爲subject中的靜態類Builder設置SecurityManager實例、SubjectContext實例(new DefaultSubjectContext()), 併爲subjectContext設置securityManager對象,the following simple code: subject builder.securityManager = securityManager; subject builder.subjectContext = newSubjectContextInstance(); subject builder.subjectContext.setSecurityManager(securityManager);
    2. 利用step 1中的Subject.Builder對象建立Subject 對象: Subject.Builder.buildSubject(); -> 將建立Suject的任務delegate to SecurityManager 對象上 -> builder.securityManager.createSubject(SubjectContext subjectContext); -> 拷貝參數中的subjectContext, 保證建立subject期間不修改subjectContext參數: securityManager.copy(subjectContext); 確保context中存在securityManager, 經過以前copy subjectContext 將origin subjectContext放到backing map中: securityManager.ensureSecurityManager(subjectContext); -> 若是session對象不爲空,將其設置到subjectContext中: resolveSession(subjectContext); -> 設置PrincipalCollection對象到subjectContext中: resolvePrincipals(subjectContext); -> 設置完subjectContext,開始利用subjectContext初始化Subject對象: doCreateSubject(subjectContext); -> securityManager.getSubjectFactory().createSubject(subjectContext); -> 幾回設置subjectContext後,經過subjectContext實例化DelegatingSubject: return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
    3. 獲得step 2的Subject對象(DelegatingSubject)以後,save(Subject subject); [note: 後續對subject的引用,引用的場景: 用戶登錄時,若是須要rememberMe功能,須要解析principals 而且將principals存儲到session中, 這樣每次用戶每一個操做不用常常性的從新組裝rememberMe pricipalCollection]
    4. 先判斷是否支持session存儲subject,利用SessionStorageEvaluator判斷: getSessionStorageEvaluator().isSessionStorageEnable(subject); -> [No: return Subject(意味着用戶每次操做須要作認證) , Yes: subjectDAO.saveToSession(subject);(將subject對象保存到Subject.getSession()中)]
    5. 將subject's state(特指: subject's principals and subject's authentication state): DefaultSubjectDAO.saveToSession(subject); 這個方法分2個方法save subject's state: I) mergePrincipals(subject); II) mergeAuthenticationState(subject); 逐一剖析2個方法--------->
      1. mergePrincipals(subject); [note: 更新最新的principalCollection到subject.getSession()中]
      2. mergeAuthenticationState(subject); [note: 更新最新的authentication state 到subject.getSession()中]
      3. return subject;
    6. 獲得subject對象,將其綁定到當前線程上: ThreadContext.bind(Subject subject); [note: 利用ThreadLocal,存儲類型爲Map<String, Object>]
    7. 返回Subject 對象
  6. 獲取當前用戶session: subject.getSession() ==等價於==> subject.getSession(true); [若是session對象爲null, 自動初始化Session], 剖析初始化session [note: 類關係 DefaultSessionManager extends AbstractValidatingSessionManager extends AbstractNativeSessionManager extends AbstractSessionManager implements SessionManager]:
    1. 建立SessionContext extends Map<String, Object>, 用其默認實現初始化: SessionContext sessionContext = new DefaultSessionContext(); 並設置請求host: sessionContext.setHost(host);緩存

    2. 利用 securityManager建立session: subject.securityManager.start(sessionContext);[note: securityManager將建立session任務代理到 SessionManager, securityManager.sessionManager.start(sessionContext);] 剖析AbstractNativeSecurityManager.start(sessionContext);session

      1. AbstractValidatingSessionManager.createSession(sessionContext); -> 檢查是否作session超時驗證 [Yes: 實例化定時器單線程Executor: ScheduledExecutorService, 設置爲daemon thread, 線程任務:定時掃描全部session, 檢查是否expire?!]
      2. 真正建立session對象: DefaultSessionManager.doCreateSession(sessionContext); -> SimpleSessionFactory.createSession(sessionContext); -> 經過sessionContext獲取host, 設置到初始化的session對象中: sessionContext.get("host"); return new SimpleSession();
      3. create(session); -> 從step 2 建立SimpleSession後, 爲其設置惟一標識:session id(由SessionIdGenerator#generateId(session)生成惟一session id)並將設置session id 以後的session對象存放在SessionDAO的sessions map中(Map類型ConcurrentMap<Serializable, Session>)中: sujectDAO.create(session);
      4. 設置全局session超時時間: applyGlobalSessionTimeout(session);
      5. SessionManager#start()時,想在全部session listeners以前執行動做: onStart(Session, SessionContext);
      6. 觸發全部監聽session的listeners: notifyStart(session, context);
      7. 通過上述6步已經完成session的初始化, 可是不會把已經建立的session直接返回到client供程序猿使用, 還會再包裝一層:createExposedSession(session, SessionContext); -> return new DelegatingSession(SessionManager, SessionKey); -> DelegatingSession初始化參數具體化: new DelegationSession(DefaultSessionManager, new DefaultSessionKey(session.getId())); -> 若是隻返回(DelegatingSession implements Session), 客戶端層面(程序猿)只能經過DelegatingSession操做原始session對象,方式:sessionManager 裏有sessionDAO, sessionDAO裏有Map類型的sessions, 經過session key(Serializable 類型) 獲得原始session, 便可以經過DelegatingSession操做原始session.
      8. 已經成功建立delegatingSession,按理說能夠返回給程序猿了吧, 可是?!還不行, 還須要包裝一層!!!再返回給程序猿,我擦, 等session等的花都謝了,好吧,上代碼: DelegatingSubject.session = decorate(delegatingSession); -> return new StoppingAwareProxiedSession(DelegatingSession, DelegatingSubject); [note: StoppingAwareProxiedSession 見文知意: 能夠stop的ProxiedSession] -> 貼個StoppingAwareProxiedSession's source code: ---> private class StoppingAwareProxiedSession extends ProxiedSession {

      private final DelegatingSubject owner;app

      private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) { super(target); owner = owningSubject; }函數

      public void stop() throws InvalidSessionException { super.stop(); owner.sessionStopped(); } } [note: 這裏爲何要用StoppingAwareProxiedSession包裝delegatingSession呢?從實現上來看它繼承ProxiedSession,並重寫了stop()方法,目的有二: I) 經過StoppingAwareProxiedSession進一步代理DelegatingSession;II) DelegatingSubject中銷燬session方法不能爲public的,經過重寫stop()方法使得能夠在DelegatingSubjct銷燬而且能夠代理到ProxiedSession#stop() -> delegatingSession#stop(); -> 原始sesion#stop(); -> 無內存泄漏] 9. OK, 經過step 8的再次封裝獲得原始session的最終代理對象 StoppingAwareProxiedSession, 將其設置給DelegatingSubject,供程序猿調用: delegatingSubejct.session = StoppingAwareProxiedSession;源碼分析

  7. 獲得代理session後,作了一個小練習,從session設置屬性, 獲取屬性: session.setAttribute("username", "kitty"); session.getAttribute("username");
  8. 利用初始化的subject(DelegatingSubject), 嘗試登陸操做: delegatingSubject.login(UsernamePasswordToken); 如下剖析login(token)過程:
    1. 一個用戶想要login: delegatingSubject.login(UsernamePasswordToken);
    2. login(token); -> 若是session中存在特定key先清除: DelegatingSubject#clearRunAsIdentitiesInternal();[session特定key: private static final String RUN_AS_PRINCIPALS_SESSION_KEY = DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";] -> 真正的登陸操做在SecurityManager中: DelegatingSubject#login(token); 代理到 DelegatingSubject.getSecurityManager().login(DelegatingSubejct, token); !!! 剖析securityManager.login(DelegatingSubject, UsernamePasswordToken);[note: 剖析以前先理清類關係: 1. SecurityManager是一個集成接口,它分別繼承了3個接口 SecurityManager implements Authorizer, Authenticator, SessionManager. SecurityManager 存在的意義更多的是把這3個接口功能(驗證、鑑權、session管理器)集於一身,用戶只需經過一個接口統一調用,這三個接口任意實現組合構成一個securityManager,符合對象的多態特徵,而且更易擴展, SecurityManager 擴展的特定接口關係以下: DefaultSecurityManager extends SessiosSecurityManager extends AuthorizingSecurityManager extends AuthenticatingSecurityManager extends RealmSecurityManager(??? 不太清楚此抽象類功能) extends CachingSecurityManager implements (SecurityManager, EventBusAware, CacheManagerAware, Destroyable)
      1. 利用SecurityManager的抽象實現:AuthenticatingSecurityManager作驗證,這個抽象實現組合Authenticator 接口作驗證,在AuthenticatingSecurityManager的構造函數中,設置默認的驗證明現: Authenticator authenticator = new ModularRealmAuthenticator(); -> 開始驗證: authenticator.authenticate(token); -> 調用Authenticator的抽象實現AbstractAuthenticator.authenticate(AuthenticationToken); [note: AbstractAuthenticator 是全部Authenticator具體實現的模板方法, 即 AbstractAuthenticator會定義驗證流程(方式),固然具體的驗證策略會抽象出來,讓具體Authenticator實現驗證策略, 抽象的驗證策略: doAuthenticate(AuthenticationToken);] 剖析 AbstractAuthenticator的模板式的驗證:AbstractAuthenticator.authenticate(AuthenticationToken);
        1. 抽象出驗證策略: AuthenticationInfo info = AbstractAuthenticator.doAuthenticate(AuthenticationToken); [留給Authenticator的具體實現完成]測試

        2. 若是step 1返回的AuthenticationInfo爲空, 則拋出shiro自定義的驗證異常: throw new AuthenticationException("refuse login sys"); -> 通知全部authenticator的監聽者驗證失敗: notifyFailure(AuthenticationToken, AuthenticationException);ui

        3. 若是step 1中返回的AuthenticationInfo 不爲空, 通知監聽驗證的全部觀察者: notifySuccess(AuthenticationToken, AuthenticationInfo); -> 最終返回已經過認證的信息: return AuthenticationInfo; 模板式的身份驗證已經呈如今上述3個步驟中, 接下來詳細說下Authenticator的具體實現: ModularRealmAuthenticator.doAuthenticate(AuthenticationToken); 1. 先校驗Authenticator中的Collection<Realm> 是否爲空,若是爲空, 則拋出異常: throw new IllegalStateException("Realm instance must exist at least one! Realm is used to execute AN authentication attemp!"); [note: Realm 做用是啥???先繼續, 邊讀源碼 邊理解]this

          2. 若是step 1中返回的Collection<Realm>#size() == 1 執行單個realm驗證: doSingleRealmAuthentication(Realm, AuthenticationToken); 不然執行多realm 驗證: doMultiRealmAuthentication(Collection<Realm>, AuthenticationToken); 這裏先考慮單realm 驗證: doSingleRealmAuthentication(Realm, AuthenticationToken); 剖析下單realm 驗證明現: 
           	1. 若是單個realm不支持用AuthenticationToken認證, 拋出異常: throw new UnSupportedTokenException("the realm instance does not support such an authentication token!");
           	2. 若是單個realm支持 利用AuthenticationToken這樣的認證, then execute realm authentication: IniRealm.getAuthenticationInfo(AuthenticationToken); -> AuthenticatingRealm.getAuthenticationInfo(AuthenticationToken); -> 先嚐試從緩存中獲得AuthenticationInfo, 緩存k,v => <Object, AuthenticationInfo> => <AuthenticationToken, AuthenticationInfo>: getCachedAuthenticationInfo(AuthenticationToken); -> 先想方設法獲得Cache對象, 而後再從Cache中得到對應的AuthenticationInfo, 若是Cache中真的沒有咱們想要的AuthenticationInfo, 那麼就perform the lookup: doGetAuthenticationInfo(AuthenticationToken); 先看看怎麼獲得Cache對象: getCachedAuthenticationInfo(AuthenticationToken) -> getAvailableAuthenticationInfoCache() -> 確認是否能夠緩存Authentication信息, 若是能夠經過CacheManager.getCache(cacheName); 獲得緩存: getAuthenticationCacheLazy(); -> getCacheManager().getCache(getAuthenticationCacheName()); 在本次測試代碼中,是不容許緩存緩存Authentication信息的, 因此跳過了getCacheManager().getCache(getAuthenticationCacheName()); 直接獲取 AuthenticationInfo: doGetAuthenticationInfo(AuthenticationInfo); [note: doGetAuthenticationInfo(AuthenticationToken); 爲抽象方法, 用戶須要自定義認證方法, 最終結果: 組裝AuthenticationInfo或者拋出認證異常 => throw new AuthenticationException("認證失敗");] -> 若是最終返回AuthenticationInfo instance, 嘗試將其按鍵值對緩存<AuthenticationToken, AuthenticationInfo> [note: 本次不開啓AuthenticationInfo緩存] -> 將獲得的AuthenticationInfo 與 AuthenticationToken 作證書對比: CredentialMatcher.match(AuthenticationToken, AuthenticationInfo);[note: 若是二者credentials不符合,then throw new AuthenticationException("submitted credentials don't match the expected credentials");]
    3. 已經過認證, 更新原來的Subject 對象, 並將其更新到SubjectDAO中: createSubject(AuthenticationToken, AuthenticationInfo, Subject);
    4. 用戶已經經過認證, 測試用戶是否有某個指定的角色: subject.hasRole("schwartz"); -> hasPrincipals() && Subject.getSecurityManager().hasRole(PrincipalCollection, roleIdentifier: "schwartz"); -> AuthorizationInfo info = securityManager.getAuthorizer().hasRole(PrincipalCollection, roleIdentifier: "schwartz") -> ModularReamlAuthorizer.hasRole(PrincipalCollection, roleIdentifier: "schwartz"); 剖析ModularRealmAuthorizer.hasRole(PrincipalCollection, roleIdentifier: "schwartz");
      1. 先校驗是否存在Collection<Realm>: assertRealmConfigured();
      2. 循環遍歷Collection<Realm>判斷其是否屬於Authorizer類型, 若是屬於, then => realm.hasRole(PrincipalCollection, roleIdentifier: "schwartz"); => AuthorizerRealm.hasRole(PrincipalCollection, roleIdentifier); -> 先獲得用戶鑑權信息,而後將鑑權信息與roleIdentifier做對比, 決定hasRole() 返回值[true or false]: getAuthorizationInfo(PrincipalCollection); 剖析getAuthorizationInfo(PrincipalCollection);[note: 這種鑑權方式與以前的驗證有類似之處,都是在抽象類中寫一個'模板'方法:設計出共有的驗證、鑑權流程, 抽象出用戶個性的驗證(getAuthenticationInfo(AuthenticationToken))、鑑權(getAuthorizationInfo())方法]
        1. 想法設法獲得shiro的緩存對象: Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();[note: 若是AuthorizationRealm.getAuthorizationCache()爲空,而且在authorizationCachingEnabled == true 的狀況下, 會用到終極得到Cache對象的方式: CacheManager.getCache();] 本次示例中 authorizationCacheingEnabled = false, 因此最終Cache<Object, AuthorizatinInfo> 爲空
        2. 在step 1 中Cache 對象爲空,then perform the lookup: AuthorizationInfo info = doGetAuthorizationInfo(PrincipalCollection);
        3. return AuthorizationInfo;
      3. 從step 2 獲得AuthenticationInfo, 將其與roleIdentifier比較: boolean hasRole = hasRole(AuthorizationInfo, roleIdentifier);
  9. 從step 4中獲得當前用戶擁有'schwartz'這個角色, 如今測試當前用戶是否擁有這樣的權限: "lightsaber:weild" -> subject.isPermitted("lightsaber:weild"); 剖析isPermitted("lightsaber:weild");
    1. hasPrincipals() && securityManager.isPermitted(PrincipalCollection, permission); [note: 先檢查當前用戶在不在線, 若是在線再去判斷他是否擁有指定權限] 剖析securityManager.isPermitted(PrincipalCollection, permission);[note: 判斷是否擁有指定權限時, 功能代理順序 Subject -> SecurityManager -> Authorizer]
      1. authorizer.isPermitted(PrincipalCollection, permission); -> 判斷當前Authorizer是否存在Collection<Realm> -> 依次遍歷Collection<Realm> 若是集合中有符合Realm instanceof Authorizer, 則利用該realm作權限判斷: isPermitted(PricipalCollection, permission);
      2. 將以前String類型的permission 轉換爲Permission類型,默認轉爲默認的Permission實現: new WildcardPermission(permission);[note: 這個構造函數,實際上是將已經用預約義格式表示的String類型的權限碼轉換成WildcarPermission中List<Set<String>> 類型的parts。 String類型的權限碼預約義表示示例:aa:bb,bbb:* 含義爲用戶具備aa權限下的bb和bbb的全部權限]
      3. 先經過getAuthorizationInfo(PrincipalCollection) 獲得AuthorizationInfo -> 經過AuthorizationInfo獲得角色和權限信息, 最終都轉化爲權限信息Collection<Permission> ,遍歷這些權限集合與指定權限對比, 最終獲取用戶是否有指定權限[return true or false]。
  10. 至此用戶從登陸到鑑權已經完成, 最後就是要註銷用戶(登出) -> subject.logout(); -> 註銷用戶就是將當前用戶所佔用的資源所有回收(清除資源消息), 大體包含的資源: Session的指定的屬性[note: 不是Session自己], Cache指定的屬性[note: 不是Cache自己], 銷燬的過程經過繼承關係先清除父類資源消息, 再自身清除資源消息 => subject.logout(); -> securityManager.logout(); -> 判斷authorizer 是否屬於LogoutAware 類型, 若是屬於該類型: authorizer.onLogout(PrincipalCollection); -> 判斷Authorizer中是否含有Collection<Realm>, 若是存在Realm集合,則依次遍歷集合, 執行以下動做: realm.onLogout(PrincipalCollection);
相關文章
相關標籤/搜索