shiro内部原理分析

一句话总结:会话域Context一路收集principals, authenticated, host, session(readSession()返回), sessionEnabled, request, response, securityManager ; 最终被存入到了返回的这个Subject中

 

1.第一步:ShiroFilterFactoryBean的初始化和创建
(1)实现BeanPostProcessor接口:具体看源码postProcessBeforeInitialization
实现的功能:将在Spring中注册的Filter并入到ShiroFilterFactoryBean的filters中,
目标:保证不遗漏Filter
(2)实现FactoryBean<T>接口:具体看源码getObject()
实现的功能: 返回AbstractShiroFilter实例,本质是个过滤器(extends OncePerRequestFilter ),所以应用必须运行在servlet容器中
-->确保用户配置了SecurityManager
-->createFilterChainManager():
-->获取默认过滤器,为默认加载的Filter,应用上全局配置属性(applyGlobalPropertiesIfNecessary)

public enum DefaultFilter {
    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
    ....
    }

 
   -->处理用户配置的ShiroFilterFactoryBean.filterChainDefinitions属性,添加到DefaultFilterChainManager对象的filterChains属性中
-->构造SpringShiroFilter实例,由Spring管理,具体看源码createInstance();
2.第二步:处理1个http请求原理
(1)1个请求1个createSubject原理:由于ShiroFilterFactoryBean本质是个AbstractShiroFilter过滤器,所以每次请求都会执行doFilterInternal里面的createSubject方法。
-->createSubject逻辑比较复杂,单独梳理。
(2)最终被并入到Servlet的FilterChain中的Filter实例为ShiroFilterFactoryBean.SpringShiroFilter类型
(3)doFilterInternal中的核心处理逻辑:executeChain

subject.execute(new Callable() {
                public Object call() throws Exception {
                    AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);
                    AbstractShiroFilter.this.executeChain(request, response, chain);
                    return null;
                }
            });

 
-->根据请求的request找到requestURI ,比如 以 http://xxx/myapp/my/test.jsp举例的话;这里返回的requestURI, 其值为 /my/test.jsp
-->筛选出与当前请求链接匹配的FilterChain:
DefaultFilterChainManager类中Map<String, Filter>类型的字段 filters ,它里面存储的是默认的Filter + 我们自定义注册的Filter
-->一旦匹配, 则对Servlet的FilterChain进行代理。保证Shiro自定义的Filter执行完毕之后,再执行Servlet的Filter。
-->总结:
Shiro直接将整个FilterChain代理,先执行完我的FilterChain,才考虑Servlet的。
Shiro 对Servlet 容器的FilterChain 进行了代理,即ShiroFilter 在继续Servlet 容器的Filter链的执行之前,
通过ProxiedFilterChain`对Servlet 容器的FilterChain 进行了代理;即先走Shiro 自己的Filter 体系,
然后才会委托给Servlet 容器的FilterChain 进行Servlet 容器级别的Filter链执行; 《跟开涛学Shiro》
每次请求的到来,Shiro都会从我们配置到org.apache.shiro.spring.web.ShiroFilterFactoryBean的filterChainDefinitions中
挑选一个匹配过滤链(多个匹配也只会选择第一个匹配的),Shiro会执行这个链条,最后采取执行其他Servlet Filter
-->
3.第三步:shiro的扩展
-->都是向FilterChain中插入自定义Filter; 玩的都是过滤器,简单好用。。。。

-->扩展CachingSessionDAO,实现session的自定义存取

-->AuthenticationListener

-->LogoutAware

-->自定义SimpleSession,新增一些属性,比如在线状态等

-->自定义SessionFactory,初始化自定义的SimpleSession


4.补充:createSubject逻辑:
-->源码入口:

// AbstractShiroFilter.createSubject
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    // securityManager是shiro强制要求用户必须自己配置的。
    // 在Web环境下,其为DefaultWebSecurityManager类型。这一点随便找个spring-shiro的配置文件就能看到了。
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
-->WebSubject.Builder构造函数:
public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
    // 调用Subject.Builder的构造函数
    super(securityManager);
    if (request == null) {
        throw new IllegalArgumentException("ServletRequest argument cannot be null.");
    }
    if (response == null) {
        throw new IllegalArgumentException("ServletResponse argument cannot be null.");
    }
    // 让SubjectContext会话域附加上当前请求request; 贯穿整个执行过程。
    setRequest(request);
    // 让SubjectContext会话域附加上当前响应response; 贯穿整个执行过程。
    setResponse(response);
}

// Subject.Builder 构造函数
public Builder(SecurityManager securityManager) {
    if (securityManager == null) {
        throw new NullPointerException("SecurityManager method argument cannot be null.");
    }
    this.securityManager = securityManager;
    // 构建一个SubjectContext会话域。
    this.subjectContext = newSubjectContextInstance();
    if (this.subjectContext == null) {
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                "cannot be null.");
    }

    // 让会话域带着securityManager贯穿整个执行过程。
    this.subjectContext.setSecurityManager(securityManager);
}

 
-->以上是初始化上下文,然后哒哒哒。。快马加鞭。,我们直接跳到WebSubject.Builder.buildWebSubject:
public Subject buildSubject() {
    // 委托给了SecurityManager实例;
    // 这里的securityManager实际类型为DefaultWebSecurityManager类型
    // 而subjectContext的时机类型为DefaultWebSubjectContext, 而且按照之前的跟踪,
    //我们知道该Context中已经被填入了当前请求的request,response以及securityManager实例
    return this.securityManager.createSubject(this.subjectContext);
}
总之就是把创建subject的操作直接委托给了SecurityManager实例创建subject(携带subjectContext,这个上下文很重要)
-->
会话域Context一路收集来的principals, authenticated, host, session, sessionEnabled, request, response, securityManager ; 最终被存入到了返回的这个Subject中
--> 核心:DefaultSecurityManager.createSubject:

public Subject createSubject(SubjectContext subjectContext) {
    //create a copy so we don't modify the argument's backing map:
    // copy方法被子类DefaultWebSecurityManager重载; 返回一个DefaultWebSubjectContext实例
    SubjectContext context = copy(subjectContext);

    //ensure that the context has a SecurityManager instance, and if not, add one:
    // 子类DefaultWebSecurityManager未进行重载, 
    // 此方法确保会话域持有一个SecurityManager来贯穿整个执行流程。
    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:
    // 向会话域中存入一个Session实例; 
    // 此操作可能失败, 届时会构建一个缺少Session的会话域 
    // 注意这里的构建Session出错是被允许的, 所以异常是以Debug的方式输出的.
    // Session的维护是交给了专门的SessionManager来负责
    // 注意这里用的是SessionKey类型的Key,而不是简单的string类型的sessionId
    // 因为Session我们操作的比较频繁,所以下文会进行详解
    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:
    // 这一步会向会话域中插入Principal; 此操作也有可能失败, 即最终会话域context中缺少Principal信息
    // rememberMe的功能也是交给了专门的RememberMeManager
    // 而且默认的RememberMe功能是通过Cookie来完成的, 所以默认的实现是CookieRememberMeManager; 而且cookie的默认名称是rememberMe
    // 而且Shiro有自己专门的Cookie接口,而唯一的实现则是SimpleCookie
    context = resolvePrincipals(context);

    // 看过Spring源码的都知道这命名意味着什么, 真正干活的来了。
    // 创建Subject的工作又被委派给了专门的SubjectFactory, 七拐八绕啊。
    // SubjectFactory接口的默认实现为DefaultWebSubjectFactory
    // 观察其对createSubject方法的实现正式将会话域context这一路收集来的信息汇总生成一个WebDelegatingSubject实例(又增加一个中间层)。
    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:
    // 专门的SubjectDAO接口负责对该subject进行保存操作
    // SubjectDAO接口的默认实现类为DefaultSubjectDAO
    save(subject);

    return subject;
}

 

public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
    public DefaultWebSubjectFactory() {
    }

    public Subject createSubject(SubjectContext context) {
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        } else {
            WebSubjectContext wsc = (WebSubjectContext)context;
            SecurityManager securityManager = wsc.resolveSecurityManager();
            Session session = wsc.resolveSession();
            boolean sessionEnabled = wsc.isSessionCreationEnabled();
            PrincipalCollection principals = wsc.resolvePrincipals();
            boolean authenticated = wsc.resolveAuthenticated();
            String host = wsc.resolveHost();
            ServletRequest request = wsc.resolveServletRequest();
            ServletResponse response = wsc.resolveServletResponse();
            return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
        }
    }
...
}

 
-->context = resolveSession(context)分析
最终是执行:DefaultSessionManager类中的一段逻辑:retrieveSessionFromDataSource:
看到这个方法的代码我们就知道是通过外部的sessionDao去读了,而这个Dao恰好是我们经常扩展的Dao,比如改成从redis存取数据

protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = this.getSessionId(sessionKey);
        if (sessionId == null) {
            log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a session could not be found.", sessionKey);
            return null;
        } else {
            Session s = this.retrieveSessionFromDataSource(sessionId);
            if (s == null) {
                String msg = "Could not find session with ID [" + sessionId + "]";
                throw new UnknownSessionException(msg);
            } else {
                return s;
            }
        }
    }
    
    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
        return this.sessionDAO.readSession(sessionId);
    }

 
   
    -->context = this.resolvePrincipals(context);分析:
    从RememberMeManager中获取凭证

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值