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

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

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

Subject 是Shiro中核心的也是我们经常用到的一个对象,那么Subject 对象是怎么构造创建,并如何存储绑定供程序调用的,下面我们就对其流程进行一下探究,首先是Subject 接口本身的继承与实现,这里我们需要特别关注下Web 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对象,一个基于ThreadLocal的上下文管理容器,里面定义了一个InheritableThreadLocalMap

首先我们看下绑定操作的入口,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 execute(Callable callable) throws ExecutionException {
        Callable associated = associateWith(callable);//初始化一个SubjectCallable对象,并把回调方法传进去
        try {
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }

    public  Callable associateWith(Callable callable) {
        return new SubjectCallable(this, callable);
    }

看下SubjectCallable类的具体实现

public class SubjectCallable implements Callable {

    protected final ThreadState threadState;
    private final Callable callable;

    public SubjectCallable(Subject subject, Callable delegate) {
        this(new SubjectThreadState(subject), delegate);//初始化构造方法
    }

    protected SubjectCallable(ThreadState threadState, Callable 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 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> resources = new InheritableThreadLocalMap>();//定义一个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 DelegatingSubject对象的过程;Subject对象创建后,会通过Shiro底层维护的一个基于ThreadLocal的上下文管理容器,即ThreadContext这个类,与请求所属的线程进行绑定,供后续访问使用。对Subject对象创建与绑定流程的分析,有助于理解Shiro底层的实现机制与方法,加深对Shiro的认识,从而在项目中能够正确使用。希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

Original: https://www.cnblogs.com/dafanjoy/p/14343168.html
Author: DaFanJoy
Title: Shiro中Subject对象的创建与绑定流程分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714231/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【HTTPS】1、使用jdk实现https接口调用和证书验证

    概述 我们之前调用https都是不做证书验证的,因为我们实现X509TrustManager方法的时候并没有具体实现里面的方法,而是不实现,那么这就会导致一个问题,那就是证书有正确…

    技术杂谈 2023年7月24日
    075
  • [每日电路图] 12、带自动烧写能力的 ESP8266 开发板制作

    前言 1、芯片先关信息 2、原理图介绍 2.1 供电电路 2.2 串口电路 2.3 自动烧写电路 3、PCB 效果展示 附录 前言 ESP8266 是乐鑫公司面向物联网应用的高性价…

    技术杂谈 2023年6月1日
    0120
  • 国运之争 功在当下

    国运之争 中华复兴 郭继承讲的好 国运之争 系于当代,下一代 树报国志 成栋梁材 十年累积 三年中考 三年高考 四年大学 道德在内修心思价值观念 仁爱在外制态度情绪言行 业果在平时…

    技术杂谈 2023年5月31日
    086
  • OS第三章错题补充

    OS第三章错题补充 ​ 批处理作业调度原则:公平性、极大的流量、平衡资源使用 ​ ​ 每个进程申请该类资源最多为4,6*3=18,再加上一个额外的资源,所以20个资源完全够6个程序…

    技术杂谈 2023年7月11日
    081
  • 网络设备配置-10、利用ACL配置访问控制

    一、前言 同系列前几篇:网络设备配置–1、配置交换机enable、console、telnet密码网络设备配置–2、通过交换机划分vlan网络设备配置&#8…

    技术杂谈 2023年7月11日
    098
  • Goroutines (一)

    Goroutines CSP communicating sequential processes Go 语言中,每一个并发执行单元叫做一个goroutine,语法上仅需要在一个普…

    技术杂谈 2023年7月11日
    059
  • JavaCV的摄像头实战之五:推流

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是《JavaCV的摄…

    技术杂谈 2023年7月11日
    098
  • 上周热点回顾(7.18-7.24)

    热点随笔: · 聊一聊 C# 后台GC 到底是怎么回事? (一线码农)· 从工程师到技术leader思维升级 (温一壶清酒)· 超酷炫的转场动画?CSS 轻松拿下!(ChokCoc…

    技术杂谈 2023年5月31日
    092
  • 四类日志

    系统 代理 网关 操作 posted @2022-08-04 14:42 papering 阅读(11 ) 评论() 编辑 Original: https://www.cnblog…

    技术杂谈 2023年5月31日
    0109
  • 树莓派4安装vim8.2编译python3支持

    主机版本:树莓派4b操作系统:Linux raspberrypi 5.10.52-v7l+ #1440 SMP Tue Jul 27 09:55:21 BST 2021 armv7…

    技术杂谈 2023年7月23日
    077
  • 关于CPU的一些基本知识总结

    关于CPU和程序的执行 CPU是计算机的大脑。 程序的运行过程,实际上是程序涉及到的、未涉及到的一大堆的指令的执行过程。 当程序要执行的部分被装载到内存后,CPU要从内存中取出指令…

    技术杂谈 2023年5月31日
    0124
  • 性能测试案例全过程方案七———购物流程(重要!!!)

    性能测试案例全过程方案七——-混合场景 Ultimate Thread Group梯度线程 梯度加压测试可以使用插件:Ultimate Thread Gro…

    技术杂谈 2023年5月31日
    0103
  • Javaer 面试必背系列!超高频八股之三色标记法

    可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文提到过,在可达性分析中,第一阶段 “根节点枚举” 是必须 STW 的,不然如果分析过程中…

    技术杂谈 2023年7月24日
    083
  • 单调栈

    栈 栈是 OI 中常用的一种线性数据结构。 栈的修改是按照后进先出的原则进行的,因此栈通常被称为是后进先出(last in first out)表,简称 LIFO 表。 下文均使用…

    技术杂谈 2023年7月23日
    065
  • 官方文档:grep 命令

    2 Invoking grep The general synopsis of the grep command line is There can be zero or more…

    技术杂谈 2023年5月31日
    095
  • GRU

    在神经网络发展的过程中,几乎所有关于LSTM的文章中对于LSTM的结构都会做出一些变动,也称为LSTM的变体。其中变动较大的是门控循环单元(Gated Recurrent Unit…

    技术杂谈 2023年7月11日
    094
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球