Java   发布时间:2022-04-01  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了Shiro中Subject对象的创建与绑定流程分析大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

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

Shiro中Subject对象的创建与绑定流程分析

 一、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对象,@H_450_17@@H_450_17@一个基于@H_450_17@ThreadLocal的上下文管理容器,里面定义了一个@H_450_17@InheritableThreadLocalMap<Map<Object, Object>>(),Subject最后就是被放到这个map当中,我们获取时也是从这个map中获取

@H_450_17@@H_450_17@@H_450_17@@H_450_17@首先我们看下绑定操作的入口,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对象,@H_450_17@@H_450_17@一个基于@H_450_17@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上下文对象进行赋值与完善,并最终构造返回Web@H_450_17@DelegaTingSubject对象的过程;Subject对象创建后,会通过Shiro底层维护的@H_450_17@@H_450_17@一个基于@H_450_17@ThreadLocal的上下文管理容器,即@H_450_17@ThReadContext这个类,与请求所属的线程进行绑定,供后续访问使用。对Subject对象创建与绑定流程的分析,有助于理解Shiro底层的实现机制与方法,加深对Shiro的认识,从而在项目中能够正确使用。希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

 

大佬总结

以上是大佬教程为你收集整理的Shiro中Subject对象的创建与绑定流程分析全部内容,希望文章能够帮你解决Shiro中Subject对象的创建与绑定流程分析所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: