zoukankan      html  css  js  c++  java
  • spring mvc下shiro的session,request等问题

    最近的一个项目使用的是spring mvc,权限框架使用的是shiro.

    不过有一个问题一直困扰着我,现在的session到底是谁的session,是servlet的还是shiro的.

    于是我把spring controller参数里面的HttpServletRequest对象和HttpSession对象打印了出来

    这两个对象打印的结果是org.apache.shiro.web.servlet.ShiroHttpServletRequest和org.apache.shiro.web.servlet.ShiroHttpSession

    在不使用shiro时这些对象应该均为tomcat所实现的类,这说明在shiro执行filter时将request对象包装成了shiro实现的类.

    那么shiro是在什么时候将request对象包装了的呢?

    先看一下shiro的拦截器类图:

    ShiroFilter 是整个 Shiro 的入口点,用于拦截需要安全控制的请求进行处理。ShiroFilter 继承自AbstractShiroFilter,而AbstractShiroFilter继承自OncePerRequestFilter

    先看一下OncePerRequestFilter的源码 :

    public abstract class OncePerRequestFilter extends NameableFilter {
    
     
    
        // 已过滤属性的后缀名
    
        public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    
     
    
        // 是否开启过滤功能
    
        private boolean enabled = true;
    
     
    
        public boolean isEnabled() {
    
            return enabled;
    
        }
    
     
    
        public void setEnabled(boolean enabled) {
    
            this.enabled = enabled;
    
        }
    
     
    
        public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            // 获取 Filter 已过滤的属性名
    
            String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    
            // 判断是否已过滤
    
            if (request.getAttribute(alreadyFilteredAttributeName) != null) {
    
                // 若已过滤,则进入 FilterChain 中下一个 Filter
    
                filterChain.doFilter(request, response);
    
            } else {
    
                // 若未过滤,则判断是否未开启过滤功能(其中 shouldNotFilter 方法将被废弃,由 isEnabled 方法取代)
    
                if (!isEnabled(request, response) || shouldNotFilter(request)) {
    
                    // 若未开启,则进入 FilterChain 中下一个 Filter
    
                    filterChain.doFilter(request, response);
    
                } else {
    
                    // 若已开启,则将已过滤属性设置为 true(只要保证 Request 中有这个属性即可)
    
                    request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    
                    try {
    
                        // 在子类中执行具体的过滤操作
    
                        doFilterInternal(request, response, filterChain);
    
                    } finally {
    
                        // 当前 Filter 执行结束需移除 Request 中的已过滤属性
    
                        request.removeAttribute(alreadyFilteredAttributeName);
    
                    }
    
                }
    
            }
    
        }
    
     
    
        protected String getAlreadyFilteredAttributeName() {
    
            String name = getName();
    
            if (name == null) {
    
                name = getClass().getName();
    
            }
    
            return name + ALREADY_FILTERED_SUFFIX;
    
        }
    
     protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    
            return isEnabled();
    
        }
    
     protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
    
            return false;
    
        }
    
     
    
        protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException;
    
    }

    我们看到OncePerRequestFilter里面实现了Servlet规范的doFilter(),并且将该方法声明为final,可以看出shiro不允许其子类再复写该方法.

    但OncePerRequestFilter并没有实现 doFilterInternal(request, response, filterChain),这说明该方法是需要在子类中实现的.

    再来看一下OncePerRequestFilter的子类AbstractShiroFilter:

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException {
    
            Throwable t = null;
    
            try {
    
                // 返回被 Shiro 包装过的 Request 与 Response 对象
    
                final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
    
                final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    
                // 创建 Shiro 的 Subject 对象
    
                final Subject subject = createSubject(request, response);
    
                // 使用异步的方式执行相关操作
    
                subject.execute(new Callable() {
    
                    public Object call() throws Exception {
    
                        // 更新 Session 的最后访问时间
    
                        updateSessionLastAccessTime(request, response);
    
                        // 执行 Shiro 的 Filter Chain
    
                        executeChain(request, response, chain);
    
                        return null;
    
                    }
    
                });
    
            } catch (ExecutionException ex) {
    
                t = ex.getCause();
    
            } catch (Throwable throwable) {
    
                t = throwable;
    
            }
    
            if (t != null) {
    
                if (t instanceof ServletException) {
    
                    throw (ServletException) t;
    
                }
    
                if (t instanceof IOException) {
    
                    throw (IOException) t;
    
                }
    
                throw new ServletException(t);
    
            }
    
        }

    果然,在子类AbstractShiroFilter中实现了doFilterInternal()方法.

    其中用于包装request的函数:

    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
            ServletRequest toUse = request;
            if(request instanceof HttpServletRequest) {
                HttpServletRequest http = (HttpServletRequest)request;
                toUse = this.wrapServletRequest(http);
            }
    
            return toUse;
        }
    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
            return new ShiroHttpServletRequest(orig, this.getServletContext(), this.isHttpSessions());
        }

    现在终于知道shiro是怎么把request对象包装成ShiroHttpServletRequest类型的了.

    一开始session的困惑也能解开了:

    因为session是通过request获取的,所以先看一下ShiroHttpServletRequest获取session的源码:

    public HttpSession getSession(boolean create) {
            HttpSession httpSession;
            if(this.isHttpSessions()) {
                httpSession = super.getSession(false);
                if(httpSession == null && create) {
                    if(!WebUtils._isSessionCreationEnabled(this)) {
                        throw this.newNoSessionCreationException();
                    }
    
                    httpSession = super.getSession(create);
                }
            } else {
                if(this.session == null) {
                    boolean existing = this.getSubject().getSession(false) != null;
                    Session shiroSession = this.getSubject().getSession(create);
                    if(shiroSession != null) {
                        this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
                        if(!existing) {
                            this.setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
                        }
                    }
                }
    
                httpSession = this.session;
            }
    
            return httpSession;
        }

    这里主要讲的是:

    如果this.isHttpSessions()返回true,则返回父类HttpServletRequestWrapper的

    也就是servelet规范的session,否则返回ShiroHttpSession对象.

    那么this.isHttpSessions()是什么呢?

    
    
    protected boolean httpSessions = true;

    public
    boolean isHttpSessions() { return this.httpSessions; }

    this.isHttpSessions()返回ShiroHttpServletRequest对象的一个属性值,默认是true.

    ShiroHttpServletRequest的构造函数是这样的:

    public ShiroHttpServletRequest(HttpServletRequest wrapped, ServletContext servletContext, boolean httpSessions) {
            super(wrapped);
            this.servletContext = servletContext;
            this.httpSessions = httpSessions;
        }

    这说明在ShiroHttpServletRequest对象生成之初就必须指定httpSessions的值.

    再回到刚才shiro包装request的地方.

    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
            return new ShiroHttpServletRequest(orig, this.getServletContext(), this.isHttpSessions());
        }
    protected boolean isHttpSessions() {
            return this.getSecurityManager().isHttpSessionMode();
        }

    这里的this.isHttpSessions()取决于this.getSecurityManager().isHttpSessionMode()的值.

    我们项目里SecurityManager设置为DefaultWebSecurityManager.其中isHttpSessionMode()方法为:

    public boolean isHttpSessionMode() {
            SessionManager sessionManager = this.getSessionManager();
            return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
        }

    我们这个项目使用的sessionManager是DefaultWebSessionManager,DefaultWebSessionManager实现了sessionManager 接口.

    但是DefaultWebSessionManager中该方法返回的是false.

    public boolean isServletContainerSessions() {
            return false;
        }

    所以最终session得到的是ShiroHttpSession.

    转载请注明出处http://www.cnblogs.com/vinozly/p/5080692.html

  • 相关阅读:
    2017-2018-1 团队名称 第一周 作业
    20162307 2017-2018-1 《程序设计与数据结构》第3周学习总结
    20162307 2017-2018-1 《程序设计与数据结构》第1周学习总结
    20161207 结对编程-马尔可夫链-自动生成短文
    20162307 2016-2017-2《程序设计与数据结构》课程总结
    20162307 实验五 网络编程与安全
    20162307 结对编程-四则运算(挑战出题)
    20162306 2016-2017-2《程序设计与数据结构》课程总结
    实验五 网络编程与安全实验报告 20162306陈是奇
    实验补充
  • 原文地址:https://www.cnblogs.com/vinozly/p/5080692.html
Copyright © 2011-2022 走看看