• shiro源码篇


    前言

      开心一刻

        小明的朋友骨折了,小明去他家里看他。他老婆很细心的为他换药,敷药,然后出去买菜。小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就是她把我打骨折的。

    揣摩下此刻男人的内心

      路漫漫其修远兮,吾将上下而求索!

      github:https://github.com/youzhibing

      码云(gitee):https://gitee.com/youzhibing

    前情回顾

      上篇中主要讲了两点认证与授权,认证主要FormAuthenticationFilter和AnonymousFilter两个filter来控制,shiro对所有请求都会先生成ProxiedFilterChain,请求会经过ProxiedFilterChain,先执行shiro的filter链,再执行剩下的servlet Filter链,最后来到我们的Controller。

      认证过程是通过filter控制实现的,我们所有的请求由shiro中3个Filter:LogoutFilter、AnonymousFilter、FormAuthenticationFilter分摊了,LogoutFilter负责/logout,AnonymousFilter负责/login和静态资源,FormAuthenticationFilter则负责剩下的(/**),三个filter只会有一个生效(注意filter的配置顺序);当FormAuthenticationFilter生效的时候会进行登录认证,认证过程:先从缓存获取authenticationInfo,没有则通过realm从数据库获取并放入缓存,然后将页面输入的用户信息(UsernamePasswordToken)与authenticationInfo进行匹配验证,认证通过会将subject中的authenticated设置成true,表示当前subject已经被认证过了。关于认证缓存,个人不建议开启,因为当修改用户信息后,不好处理缓存中的authenticationInfo,另外认证频率本来就不高,缓存的意义不大。

      一般情况下授权是通过注解方式实现的,注解配合aop会在我们的业务方法前织入前置权限检查处理,检查过程与认证过程类似:从缓存中获取authorizationInfo,没有则通过realm从数据库获取,然后放入缓存,然后将authorizationInfo与@RequiresPermissions("xxx")中的xxx来进行匹配,完成权限检查,检查通过则进入我们的目标方法,不通过则抛出异常。关于权限缓存,个人建议开启,因为权限的验证还是挺频繁的,如果不开启缓存,那么会给数据库造成一定的压力。

    遗留问题解答

      上篇遗留问题:session过期后,我们再请求,shiro是如何处理并跳转到登录页的?回答这个问题之前,我们先看看另外一个问题:

    上篇博文中讲到了登录认证成功后会将subject的authenticated设置成true,表示当前subject已经被认证过了,但是只是当前subject;
    我们可以将subject看成是request,每次请求来的时候都会将request/response对封装成subject,AbstractShiroFilter的方法doFilterInternal中有这样一个调用
    
    final Subject subject = createSubject(request, response);
    
    我们来看看createSubject的方法描述:
        Creates a  WebSubject instance to associate with the incoming request/response pair which will be used throughout the request/response execution.
        创建一个关联request/response对的WebSubject实例,用于后续request/response的执行

      也就是目前我们还只是看到了当前请求有认证状态,当前会话还没有看到认证状态;撇开shiro,如果是我们自己实现,我们会怎么实现,肯定会在subject的authenticated设置成true的时候也将认证状态也设置在session中,至于是存储在自定义session的某个标志字段(类似subject的authenticated)中,还是存储在session的attributes中(setAttribute(Object key, Object value)进行设置),看我们的需求和个人喜好。

      归纳下这个问题:shiro是如何保存当前会话认证状态的,是上述中的某种实现方式,还是shiro有另外的实现方式

      shiro是如何保存会话认证状态的

        每次请求都会生成新的subject,如果我们把认证状态只放到subject中,那么每次请求都需要进行认证,这显然是不合理的,我们需要将认证状态保存到会话(session)中,那么整个会话期间只需要认证一次即可。那么shiro是怎么实现的了,我们来看看源码,从我们Controller的doLogin方法开始

        如果我们继续跟进s.setAttribute(attributeKey, value),会发现认证状态最终存放到了SimpleSession的attributes属性中,

    private transient Map<Object, Object> attributes;

        也就是说认证状态会存在session的attributes中,正是我们上面说的方式之一,shiro没有用它特有的方式,最终在session中的存在形式如下图

        看过上篇博客的朋友应该会有印象:FormAuthenticationFilter的isAccessAllowed方法(从AuthenticatingFilter继承)中第一个认证的是subject的authenticated

        为什么获取subject的authenticated,而不是直接获取session的认证状态,我还没弄清楚为什么,难道是为了组件的分工明确? 既然shiro这么做了肯定有它的道理,我们先不纠结这个(知道的朋友可以评论区提示下)。我们知道登录成功后,subject的authenticated会被赋值成true,但是登录成功后的其他请求,比如:http://localhost:8080/own/index,subject的authenticated是什么时候在哪被赋值成true的呢?我们已经知道session中有认证状态,那么肯定是某个时候在某个地方将session中的认证状态赋值给了subject,具体是怎么样我们来跟一下源码,还记得shiro的入口filter:SpringShiroFilter,我们从SpringShiroFilter的doFilterInternal(从AbstractShiroFilter继承)方法开始

        可以看到,在创建subject的时候,会将session中的认证状态赋值给subject的authenticated。

        小结下:登录时,登录成功会将认证状态(成功)存储到session的attributes中,之后的每一次请求,在创建subject的时候,都会将session中的认证状态赋值给subject的authenticated,那么FormAuthenticationFilter在认证的时候会直接返回true,继续走servlet filter链,最终来到我们的Controller。

        至此,该问题就明了了,会话认证状态还是保存在session中,只是中间处理的时候会将session中的认证状态赋值给subject,由subject传递给FormAuthenticationFilter认证状态。

      session过期后,我们再请求,shiro是如何处理并跳转到登录页的

        如果我们明白了上个问题,那么这个问题就很好理解了。如果session过期,那么通过sessionDAO获取的session为null,subject的authenticated就会被赋值成false,那么在FormAuthenticationFilter中认证不通过,则会重定向到/login,让用户重新进行登录认证。事实是这样吗,我们来跟下源码(假设此时请求是:http://localhost:8080/own/index)

        可以看到,请求进过SpringShiroFilter时,subject中authenticated被设置成false,然后生成ProxiedFilterChain

        请求会来到FormAuthenticationFilter,认证不通过,重定向到/login,并返回false,表示filter链不用继续往下走了(具体可查看上篇博文)。

        强调下:很多对session的操作,都会同步到缓存(或持久层),包括session刷新(touch())、设置session属性(setAttribute()等等,具体可以看AbstractNativeSessionManager,很多session操作中都会调用onChange方法

    protected void onChange(Session session) {
        sessionDAO.update(session);
    }

    shiro源码系列

      系列地址

      shiro源码篇 - shiro的session创建,你值得拥有

        SessionManager负责session的操作,包括创建、维护、删除、失效、验证等;

        AbstractNativeSessionManager的start是创建session的入口;

        SimpleSession是shiro完完全全的自己实现,是shiro对session的一种拓展。但SimpleSession不对外暴露,我们一般操作的是SimpleSession的代理:DelegatingSession,或者是DelegatingSession的代理:StoppingAwareProxiedSession;对session的操作,会通过一层层代理,来到DelegatingSession,DelegatingSession将session的操作转交给sessionMananger,sessionManager通过一些校验后,最后转交给SimpleSession处理。

      shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

        一般操作的session是session的代理,代理将session操作委托给sessionManager,sesionManager校验之后再转交给SimpleSession;

        session过期定时任务默认60分钟执行一次,所session已过期或不合法,则抛出对应的异常,上层通过捕获异常从sessionDAO中删除session;

        不只定时任务做session的校验,session的基本操作都在sessionManager中有做session的校验,例如touch、setAttribute等,具体可以查看AbstractNativeSessionManager,对session的操作都是通过AbstractNativeSessionManager处理后转交给SimpleSession。

      shiro源码篇 - shiro的session共享,你值得拥有

        session共享实现的原理其实都是一样的,都是filter + HttpServletRequestWrapper,只是实现细节会有所区别;

        shiro的session共享其实是比较简单的,重写CacheManager,将其操作指向我们的redis,然后定制CachingSessionDAO实现session缓存操作和session持久化;

        如果session需要持久化,推荐自定义sessionDAO继承EnterpriseCacheSessionDAO,如果只是缓存,则推荐自定义sessionDAO继承CachingSessionDAO。

      shiro源码篇 - shiro的filter,你值得拥有

        SpringShiroFilter注册到spring容器,会被包装成FilterRegistrationBean,通过FilterRegistrationBean注册到servlet容器;SpringShiroFilter相当于是整个shiro的入口;

        SpringShiroFilter会创建ProxiedFilterChain,代理servlet FilterChain,让请求先走shiro的filter链,让后再走servlet FilterChain。

      shiro源码篇 - shiro认证与授权,你值得拥有

        认证通过Filter实现,anon表示匿名访问,不需要认证,一般就是针对游客可以访问的资源,而authc则表示需要登录认证;

        我们所有的请求一般由shiro中3个Filter:LogoutFilter、AnonymousFilter、FormAuthenticationFilter分摊了,LogoutFilter负责/logout,AnonymousFilter负责/login和静态资源,FormAuthenticationFilter则负责剩下的(/**);

        认证由FormAuthenticationFilter实现,未登录的请求会由它重定向到/login;认证过程是将界面输入的信息(UsernamePasswordToken)与缓存(或数据库)中的authenticationInfo进行匹对验证;认证信息不建议缓存;

        授权由注解方式,配合aop实现目标方法前的增强织入;认证过程是将缓存(或数据库)中的authorizationInfo与@RequiresPermissions("xxx")中的xxx进行匹配校验;认证信息建议缓存起来。

    参考

      《跟我学shiro》

  • 相关阅读:
    Go语言通道(chan)——goroutine之间通信的管道
    GO语言数组,切片,MAP总结
    GO数组
    GO切片
    GO语言测试
    GO语言html模板
    Go语言中defer语句使用小结
    微信小程序 某个页面直接返回首页
    小程序常用变量
    bzoj1030
  • 原文地址:https://www.cnblogs.com/youzhibing/p/10154369.html
Copyright © 2020-2023  润新知