天天看点

springmvc集成shiro后,session、request姓汪还是姓蒋?

1. 疑问

我们在项目中使用了spring mvc作为mvc框架,shiro作为权限控制框架,在使用过程中慢慢地产生了下面几个疑惑,本篇文章将会带着疑问慢慢地解析shiro源码,从而解开心里面的那点小纠纠。

(1) 在spring controller中,request有何不同呢 ?

于是,在controller中打印了request的类对象,发现request对象是org.apache.shiro.web.servlet.shirohttpservletrequest ,很明显,此时的 request 已经被shiro包装过了。

(2)众所周知,spring mvc整合shiro后,可以通过两种方式获取到session:

通过spring mvc中controller的request获取session

通过shiro获取session

那么,问题来了, 两种方式获取的session是否相同呢 ?

这里需要看一下项目中的shiro的securitymanager配置,因为配置影响了shiro session的来源。这里没有配置session管理器。

在controller中再次打印了session,发现前者的session类型是 org.apache.catalina.session.standardsessionfacade ,后者的session类型是org.apache.shiro.subject.support.delegatingsubject$stoppingawareproxiedsession。

很明显,前者的session是属于httpservletrequest中的httpsession,那么后者呢?仔细看stoppingawareproxiedsession,它是属于shiro自定义的session的子类。通过这个代理对象的源码,我们发现所有与session相关的方法都是通过它内部委托类delegate进行的,通过断点,可以看到delegate的类型其实也是 org.apache.catalina.session.standardsessionfacade ,也就是说,两者在操作session时,都是用同一个类型的session。那么它什么时候包装了httpsession呢?

springmvc集成shiro后,session、request姓汪还是姓蒋?

2. 一起一层一层剥开它的芯

2.1 怎么获取过滤器filter

spring mvc 整合shiro,需要在web.xml中配置该filter

delegatingfilterproxy 是一个过滤器,准确来说是目的过滤器的代理,由它在dofilter方法中,获取spring 容器中的过滤器,并调用目标过滤器的dofilter方法,这样的好处是,原来过滤器的配置放在web.xml中,现在可以把filter的配置放在spring中,并由spring管理它的生命周期。另外,delegatingfilterproxy中的targetbeanname指定需要从spring容器中获取的过滤器的名字,如果没有,它会以filtername过滤器名从spring容器中获取。

springmvc集成shiro后,session、request姓汪还是姓蒋?

2.2 request的来源

前面说 delegatingfilterproxy 会从spring容器中获取名为 targetbeanname 的过滤器。接下来看下spring配置文件,在这里定义了一个shiro filter的工厂 org.apache.shiro.spring.web.shirofilterfactorybean。

熟悉spring 的应该知道,bean的工厂是用来生产相关的bean,并把bean注册到spring容器中的。通过查看工厂bean的getobject方法,可知,委托类调用的filter类型是springshirofilter。接下来我们看一下类图,了解一下它们之间的关系。

springmvc集成shiro后,session、request姓汪还是姓蒋?

既然springshirofilter属于过滤器,那么它肯定有一个dofilter方法,dofilter由它的父类 onceperrequestfilter 实现。onceperrequestfilter 在dofilter方法中,判断是否在request中有"already filtered"这个属性设置为true,如果有,则交给下一个过滤器,如果没有就执行 dofilterinternal( ) 抽象方法。

dofilterinternal由abstractshirofilter类实现,即springshirofilter的直属父类实现。dofilterinternal 一些关键流程如下:

在dofilterinternal中,可以看到对servletrequest和servletreponse进行了包装。除此之外,还把包装后的request/response作为参数,创建subject,这个subject其实是代理类delegatingsubject。

那么,这个包装后的request是什么呢?我们继续解析prepareservletrequest。

继续包装request,看下wrapservletrequest方法。无比兴奋啊,文章前面的shirohttpservletrequest终于出来了,我们在controller中获取到的request就是它,是它,它。它是servlet的httpservletrequestwrapper的子类。

shirohttpservletrequest构造方法的第三个参数是个关键参数,我们先不管它怎么来的,进shirohttpservletrequest里面看看它有什么用。它主要在两个地方用到,一个是getrequestedsessionid(),这个是获取sessionid的方法;另一个是getsession(),它是获取session会话对象的。

先来看一下getrequestedsessionid()。ishttpsessions决定sessionid是否来自servlet。

再看一下getsession()。ishttpsessions决定了session是否来自servlet。

既然ishttpsessions()那么重要,我们还是要看一下在什么情况下,它返回true。

ishttpsessions是否返回true是由使用的shiro安全管理器的 ishttpsessionmode() 决定的。回到前面,我们使用的安全管理器是 defaultwebsecuritymanager ,我们看一下 defaultwebsecuritymanager 的源码,找到 ishttpsessionmode 方法。可以看到,sessionmanager 的类型和 isservletcontainersessions() 起到了决定性的作用。

在配置文件中,我们并没有配置sessionmanager ,安全管理器会使用默认的会话管理器 servletcontainersessionmanager,在 servletcontainersessionmanager 中,isservletcontainersessions 返回 true 。

因此,在前面的shiro配置的情况下,request中获取的session将会是servlet context下的session。

2.3 subject的session来源

前面 dofilterinternal 的分析中,还落下了subject的创建过程,接下来我们解析该过程,从而揭开通过subject获取session,这个session是从哪来的。

回忆下,在controller中怎么通过subject获取session。

我们看一下shiro定义的session类图,它们具有一些与 httpsession 相同的方法,例如 setattribute 和 getattribute。

springmvc集成shiro后,session、request姓汪还是姓蒋?

还记得在 dofilterinternal 中,shiro把包装后的request/response作为参数,创建subject吗

subject的创建时序图

springmvc集成shiro后,session、request姓汪还是姓蒋?

最终,由 defaultwebsubjectfactory 创建subject,并把 principals, session, request, response, securitymanager这些参数封装到subject。由于第一次创建session,此时session没有实例。

那么,当我们调用 subject .getsession() 尝试获取会话session时,发生了什么呢。从前面的代码可以知道,我们获取到的subject是 webdelegatingsubject 类型的,它的父类 delegatingsubject 实现了getsession 方法,下面的代码是getsession方法中的关键步骤。

接下来解析一下,安全管理器根据会话上下文创建session这个流程,追踪代码后,可以知道它其实是交由 sessionmanager 会话管理器进行会话创建,由前面的代码可以知道,这里的sessionmanager 其实是 servletcontainersessionmanager类,找到它的 createsession 方法。

这里就可以知道,其实session是来源于 request 的 httpsession,也就是说,来源于上一个过滤器中request的httpsession。httpsession 以成员变量的形式存在 httpservletsession 中。回忆前面从安全管理器获取 httpservletsession 后,还调用 decorate() 装饰该session,装饰后的session类型是 stoppingawareproxiedsession,httpservletsession 是它的成员 。

在文章一开始的时候,通过debug就已经知道,当我们通过 subject.getsession() 获取的就是 stoppingawareproxiedsession,可见,这与前面分析的是一致的 。

那么,当我们通过session.getattribute和session.addattribute时,stoppingawareproxiedsession 做了什么?它是由父类 proxiedsession 实现 session.getattribute和session.addattribute 方法。我们看一下 proxiedsession 相关源码。

可见,getattribute 和 addattribute 由委托类delegate完成,这里的delegate就是httpservletsession 。接下来看 httpservletsession 的相关方法。

此处的httpsession就是之前从httpservletrequest获取的,也就是说,通过request.getseesion()与subject.getseesion()获取session后,对session的操作是相同的。

结论

(1)controller中的request,在shiro过滤器中的dofilterinternal方法,将被包装为shirohttpservletrequest 。

(2)在controller中,通过 request.getsession(_) 获取会话 session ,该session到底来源servletrequest 还是由shiro管理并管理创建的会话,主要由 安全管理器 securitymanager 和 sessionmanager 会话管理器决定。

(3)不管是通过 request.getsession或者subject.getsession获取到session,操作session,两者都是等价的。在使用默认session管理器的情况下,操作session都是等价于操作httpsession。

<a href="https://my.oschina.net/thinwonton/blog/979118">原文链接</a>