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);
相关文章
相关标签/搜索