MyException - 我的异常网
当前位置:我的异常网» 软件架构设计 » subject.getSession() 跟request.getSession()是同

subject.getSession() 跟request.getSession()是同一个session吗

www.MyException.Cn  网友分享于:2018-04-03  浏览:2次
subject.getSession() 和request.getSession()是同一个session吗
转载:springmvc集成shiro后,session、request姓汪还是姓蒋?
琴瑟 2017-06-30 14:26:29 浏览7493 评论1
springmvc
摘要: 1. 疑问我们在项目中使用了spring mvc作为MVC框架,shiro作为权限控制框架,在使用过程中慢慢地产生了下面几个疑惑,本篇文章将会带着疑问慢慢地解析shiro源码,从而解开心里面的那点小纠纠。

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
Session session = request.getSession();
通过shiro获取session
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
那么,问题来了, 两种方式获取的session是否相同呢 ?

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

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

        <!--<property name="sessionManager" ref="sessionManager"/>-->

        <property name="realm" ref="shiroRealm"/>
    </bean>
在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呢?

image

2. 一起一层一层剥开它的芯
2.1 怎么获取过滤器filter

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

    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shiroFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
DelegatingFilterProxy 是一个过滤器,准确来说是目的过滤器的代理,由它在doFilter方法中,获取spring 容器中的过滤器,并调用目标过滤器的doFilter方法,这样的好处是,原来过滤器的配置放在web.xml中,现在可以把filter的配置放在spring中,并由spring管理它的生命周期。另外,DelegatingFilterProxy中的targetBeanName指定需要从spring容器中获取的过滤器的名字,如果没有,它会以filterName过滤器名从spring容器中获取。

image


2.2 request的来源

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

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="successUrl" value="/main"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <property name="filters">
            <map>
                <!--表单认证器-->
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                <!-- 请求 logout地址,shiro去清除session-->
                /logout = logout
                /static/** = anon
                /** = authc
            </value>
        </property>
    </bean>
熟悉spring 的应该知道,bean的工厂是用来生产相关的bean,并把bean注册到spring容器中的。通过查看工厂bean的getObject方法,可知,委托类调用的filter类型是SpringShiroFilter。接下来我们看一下类图,了解一下它们之间的关系。


image

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

doFilterInternal由AbstractShiroFilter类实现,即SpringShiroFilter的直属父类实现。doFilterInternal 一些关键流程如下:

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

            //包装request/response
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            //创建subject,其实创建的是Subject的代理类DelegatingSubject
            final Subject subject = createSubject(request, response);

             // 继续执行过滤器链,此时的request/response是前面包装好的request/response
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
    }
在doFilterInternal中,可以看到对ServletRequest和ServletReponse进行了包装。除此之外,还把包装后的request/response作为参数,创建Subject,这个subject其实是代理类DelegatingSubject。

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

   protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest http = (HttpServletRequest) request;
            toUse = wrapServletRequest(http);  //真正去包装request的方法
        }
        return toUse;
    }
继续包装request,看下wrapServletRequest方法。无比兴奋啊,文章前面的ShiroHttpServletRequest终于出来了,我们在controller中获取到的request就是它,是它,它。它是servlet的HttpServletRequestWrapper的子类。

  protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        //看看看,ShiroHttpServletRequest
        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions()); 
    }
ShiroHttpServletRequest构造方法的第三个参数是个关键参数,我们先不管它怎么来的,进ShiroHttpServletRequest里面看看它有什么用。它主要在两个地方用到,一个是getRequestedSessionId(),这个是获取sessionid的方法;另一个是getSession(),它是获取session会话对象的。

先来看一下getRequestedSessionId()。isHttpSessions决定sessionid是否来自servlet。

public String getRequestedSessionId() {
        String requestedSessionId = null;
        if (isHttpSessions()) {
            requestedSessionId = super.getRequestedSessionId();   //从servlet中获取sessionid
        } else {
            Object sessionId = getAttribute(REFERENCED_SESSION_ID);   //从request中获取REFERENCED_SESSION_ID这个属性
            if (sessionId != null) {
                requestedSessionId = sessionId.toString();
            }
        }

        return requestedSessionId;
    }
再看一下getSession()。isHttpSessions决定了session是否来自servlet。

  public HttpSession getSession(boolean create) {

        HttpSession httpSession;

        if (isHttpSessions()) {
            httpSession = super.getSession(false);  //从servletRequest获取session
            if (httpSession == null && create) {
                if (WebUtils._isSessionCreationEnabled(this)) {
                    httpSession = super.getSession(create);  //从servletRequest获取session
                } else {
                    throw newNoSessionCreationException();
                }
            }
        } else {
            if (this.session == null) {

                boolean existing = getSubject().getSession(false) != null; //从subject中获取session

                Session shiroSession = getSubject().getSession(create); //从subject中获取session
                if (shiroSession != null) {
                    this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
                    if (!existing) {
                        setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
                    }
                }
            }
            httpSession = this.session;
        }

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

protected boolean isHttpSessions() {
        return getSecurityManager().isHttpSessionMode();
    }
isHttpSessions是否返回true是由使用的shiro安全管理器的 isHttpSessionMode() 决定的。回到前面,我们使用的安全管理器是 DefaultWebSecurityManager ,我们看一下 DefaultWebSecurityManager 的源码,找到 isHttpSessionMode 方法。可以看到,SessionManager 的类型和 isServletContainerSessions() 起到了决定性的作用。

   public boolean isHttpSessionMode() {
        SessionManager sessionManager = getSessionManager();
        return sessionManager instanceof WebSessionManager && ((WebSessionManager)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。

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession(); // session的类型?
我们看一下shiro定义的session类图,它们具有一些与 HttpSession 相同的方法,例如 setAttribute 和 getAttribute。


image


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

final Subject subject = createSubject(request, response);
subject的创建时序图

image


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

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

public Session getSession(boolean create) {
        if (this.session == null && create) {
            // 创建session上下文,上下文里面封装有request/response/host
            SessionContext sessionContext = createSessionContext();
            // 根据上下文,由securityManager创建session
            Session session = this.securityManager.start(sessionContext);
            // 包装session
            this.session = decorate(session);
        }
        return this.session;
    }
接下来解析一下,安全管理器根据会话上下文创建session这个流程,追踪代码后,可以知道它其实是交由 sessionManager 会话管理器进行会话创建,由前面的代码可以知道,这里的sessionManager 其实是 ServletContainerSessionManager类,找到它的 createSession 方法。

protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);

        // 从request中获取HttpSession
        HttpSession httpSession = request.getSession();

        String host = getHost(sessionContext);

        // 包装成 HttpServletSession
        return createSession(httpSession, host);
    }
这里就可以知道,其实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 相关源码。

public Object getAttribute(Object key) throws InvalidSessionException {
        return delegate.getAttribute(key);
    }
    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        delegate.setAttribute(key, value);
    }
可见,getAttribute 和 addAttribute 由委托类delegate完成,这里的delegate就是HttpServletSession 。接下来看 HttpServletSession 的相关方法。

  public Object getAttribute(Object key) throws InvalidSessionException {
        try {
            return httpSession.getAttribute(assertString(key));
        } catch (Exception e) {
            throw new InvalidSessionException(e);
        }
    }

    public void setAttribute(Object key, Object value) throws InvalidSessionException {
        try {
            httpSession.setAttribute(assertString(key), value);
        } catch (Exception e) {
            throw new InvalidSessionException(e);
        }
    }
此处的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。

文章评论

初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
如何成为一名黑客
如何成为一名黑客
程序员的鄙视链
程序员的鄙视链
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
中美印日四国程序员比较
中美印日四国程序员比较
代码女神横空出世
代码女神横空出世
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
一个程序员的时间管理
一个程序员的时间管理
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
 程序员的样子
程序员的样子
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员和编码员之间的区别
程序员和编码员之间的区别
10个调试和排错的小建议
10个调试和排错的小建议
那些争议最大的编程观点
那些争议最大的编程观点
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
我的丈夫是个程序员
我的丈夫是个程序员
程序员都该阅读的书
程序员都该阅读的书
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序员应该关注的一些事儿
程序员应该关注的一些事儿
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
漫画:程序员的工作
漫画:程序员的工作
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
鲜为人知的编程真相
鲜为人知的编程真相
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
每天工作4小时的程序员
每天工作4小时的程序员
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序员必看的十大电影
程序员必看的十大电影
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有