zoukankan      html  css  js  c++  java
  • Shiro中Subject对象的创建与绑定流程分析

    我们在平常使用Shrio进行身份认证时,经常通过获取Subject 对象中保存的Session、Principal等信息,来获取认证用户的信息,也就是说Shiro会把认证后的用户信息保存在Subject 中供程序使用

        public static Subject getSubject()
        {
            return SecurityUtils.getSubject();
        }

     Subject 是Shiro中核心的也是我们经常用到的一个对象,那么Subject 对象是怎么构造创建,并如何存储绑定供程序调用的,下面我们就对其流程进行一下探究,首先是Subject 接口本身的继承与实现,这里我们需要特别关注下WebDelegatingSubject这个实现类,这个就是最终返回的具体实现类

     一、Subject的创建

     在Shiro中每个http请求都会经过SpringShiroFilter的父类AbstractShiroFilte中的doFilterInternal方法,我们看下具体代码

        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
                throws ServletException, IOException {
    
            Throwable t = null;
    
            try {
                final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
                final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    
                //创建Subject
                final Subject subject = createSubject(request, response);
    
                //执行Subject绑定
                //noinspection unchecked
                subject.execute(new Callable() {
                    public Object call() throws Exception {
                        updateSessionLastAccessTime(request, response);
                        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;
                }
                //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
                String msg = "Filtered request failed.";
                throw new ServletException(msg, t);
            }
        }

    继续进入createSubject方法,也就是创建Subject对象的入口

        protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
            return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
        }

    这里使用了build的对象构建模式,进入WebSubject接口中查看Builder与buildWebSubject()的具体实现

    Builder()中主要用于初始化SecurityManager 、ServletRequest 、ServletResponse 等对象,构建SubjectContext上下文关系对象

             */
            public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
                super(securityManager);
                if (request == null) {
                    throw new IllegalArgumentException("ServletRequest argument cannot be null.");
                }
                if (response == null) {
                    throw new IllegalArgumentException("ServletResponse argument cannot be null.");
                }
                setRequest(request);
                setResponse(response);
            }

     buildWebSubject方法中开始构造Subject对象

            public WebSubject buildWebSubject() {
                Subject subject = super.buildSubject();//父类build方法
                if (!(subject instanceof WebSubject)) {
                    String msg = "Subject implementation returned from the SecurityManager was not a " +
                            WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager " +
                            "has been configured and made available to this builder.";
                    throw new IllegalStateException(msg);
                }
                return (WebSubject) subject;
            }

    进入父类的buildSubject对象我们可以看到,具体实现是由SecurityManager来完成的

            public Subject buildSubject() {
                return this.securityManager.createSubject(this.subjectContext);
            }

     在createSubject方法中会根据你的配置从缓存、redis、数据库中获取Session、Principals等信息,并创建Subject对象

        public Subject createSubject(SubjectContext subjectContext) {
            //create a copy so we don't modify the argument's backing map:
            SubjectContext context = copy(subjectContext); //复制一个SubjectContext对象
    
            //ensure that the context has a SecurityManager instance, and if not, add one:
            context = ensureSecurityManager(context); // 检查并初始化SecurityManager对象
    
            //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:
            context = resolveSession(context);//解析获取Sesssion信息
    
            //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
            //if possible before handing off to the SubjectFactory:
            context = resolvePrincipals(context);//解析获取resolvePrincipals信息
    
            Subject subject = doCreateSubject(context);//创建Subject
    
            //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:
            save(subject);
    
            return subject;
        }

    在doCreateSubject中通过SubjectFactory创建合成Subject对象

        protected Subject doCreateSubject(SubjectContext context) {
            return getSubjectFactory().createSubject(context);
        }

    我们可以看到最后返回的是具体实现类WebDelegatingSubject

        public Subject createSubject(SubjectContext context) {
            //SHIRO-646
            //Check if the existing subject is NOT a WebSubject. If it isn't, then call super.createSubject instead.
            //Creating a WebSubject from a non-web Subject will cause the ServletRequest and ServletResponse to be null, which wil fail when creating a session.
            boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
            if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {
                return super.createSubject(context);
            }
            //获取上下文对象中的信息
            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();
    
            //构造返回WebDelegatingSubject对象
            return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
                    request, response, securityManager);
        }

    以上是Subject的创建过程,创建完成后我们还需要与当前请求线程进行绑定,这样才能通过SecurityUtils.getSubject()方法获取到Subject

    二、Subject的绑定

    Subject对象本质上是与请求所属的线程进行绑定,Shiro底层定义了一个ThreadContext对象,一个基于ThreadLocal的上下文管理容器,里面定义了一个InheritableThreadLocalMap<Map<Object, Object>>(),Subject最后就是被放到这个map当中,我们获取时也是从这个map中获取

    首先我们看下绑定操作的入口,execuse是执行绑定,后续操作采用回调机制来实现

             //执行Subject绑定
                //noinspection unchecked
                subject.execute(new Callable() {
                    public Object call() throws Exception {
                        updateSessionLastAccessTime(request, response);
                        executeChain(request, response, chain);
                        return null;
                    }
                });

     初始化一个SubjectCallable对象,并把回调方法传进去

        public <V> V execute(Callable<V> callable) throws ExecutionException {
            Callable<V> associated = associateWith(callable);//初始化一个SubjectCallable对象,并把回调方法传进去
            try {
                return associated.call();
            } catch (Throwable t) {
                throw new ExecutionException(t);
            }
        }
    
    
        public <V> Callable<V> associateWith(Callable<V> callable) {
            return new SubjectCallable<V>(this, callable);
        }

    看下SubjectCallable类的具体实现

    public class SubjectCallable<V> implements Callable<V> {
    
        protected final ThreadState threadState;
        private final Callable<V> callable;
    
        public SubjectCallable(Subject subject, Callable<V> delegate) {
            this(new SubjectThreadState(subject), delegate);//初始化构造方法
        }
    
        protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
            if (threadState == null) {
                throw new IllegalArgumentException("ThreadState argument cannot be null.");
            }
            this.threadState = threadState;//SubjectThreadState对象
            if (delegate == null) {
                throw new IllegalArgumentException("Callable delegate instance cannot be null.");
            }
            this.callable = delegate;//回调对象
        }
    
        public V call() throws Exception {
            try {
                threadState.bind();//执行绑定操作
                return doCall(this.callable);//执行回调操作
            } finally {
                threadState.restore();
            }
        }
    
        protected V doCall(Callable<V> target) throws Exception {
            return target.call();
        }
    } 

    具体绑定的操作是通过threadState.bind()来实现的

        public void bind() {
            SecurityManager securityManager = this.securityManager;
            if ( securityManager == null ) {
                //try just in case the constructor didn't find one at the time:
                securityManager = ThreadContext.getSecurityManager();
            }
            this.originalResources = ThreadContext.getResources();
            ThreadContext.remove();//首先执行remove操作
    
            ThreadContext.bind(this.subject);//执行绑定操作
            if (securityManager != null) {
                ThreadContext.bind(securityManager);
            }
        }

    在上面bind方法中又会执行ThreadContext的bind方法,这里就是之前说到的shiro底层维护了的一个ThreadContext对象,一个基于ThreadLocal的上下文管理容器,bind操作本质上就是把创建的Subject对象维护到resources 这个InheritableThreadLocalMap中, SecurityUtils.getSubject()方法其实就是从InheritableThreadLocalMap中获取所属线程对应的Subject

        private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();//定义一个InheritableThreadLocalMap
    
        public static void bind(Subject subject) {
            if (subject != null) {
                put(SUBJECT_KEY, subject);//向InheritableThreadLocalMap中放入Subject对象
            }
        }
    
    
        public static void put(Object key, Object value) {
            if (key == null) {
                throw new IllegalArgumentException("key cannot be null");
            }
    
            if (value == null) {
                remove(key);
                return;
            }
    
            ensureResourcesInitialized();
            resources.get().put(key, value);
    
            if (log.isTraceEnabled()) {
                String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                        key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
                log.trace(msg);
            }
        }

     三、总结

    从以上对Shiro源码的分析,我们对Subject对象的创建与绑定进行了基本的梳理,Subject对象的创建是通过不断的对context上下文对象进行赋值与完善,并最终构造返回WebDelegatingSubject对象的过程;Subject对象创建后,会通过Shiro底层维护的一个基于ThreadLocal的上下文管理容器,即ThreadContext这个类,与请求所属的线程进行绑定,供后续访问使用。对Subject对象创建与绑定流程的分析,有助于理解Shiro底层的实现机制与方法,加深对Shiro的认识,从而在项目中能够正确使用。希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

  • 相关阅读:
    YOLO V2 代码分析
    HDU 1728 逃离迷宫【BFS】
    POJ 2987 Firing【最大权闭合图-最小割】
    POJ 2914 Minimum Cut【最小割 Stoer-Wangner】
    模拟C#的事件处理和属性语法糖
    c版基于链表的插入排序(改进版)
    一句话概述代码的用途
    用python实现的抓取腾讯视频所有电影的爬虫
    jquery 实现智能炫酷的翻页相册效果
    KISSY(JS)炫动导航,缓动应用实例(^_^)
  • 原文地址:https://www.cnblogs.com/dafanjoy/p/14343168.html
Copyright © 2011-2022 走看看