Shiro的SecurityUtils.getSubject()是如何获取到正确的用户信息

背景        

Shiro的SecurityUtils.getSubject()是如何获取到正确的用户信息的呢?昨日在测试接口时使用apifox发送请求时,我发现先使用A用户登录,再使用B用户登录,再使用A的登录凭证(jwt)去访问某个接口,使用的却是B的登录信息。这让我非常困扰。

结论

        可以把apifox当成一个浏览器,它去访问一个网站的这些接口,后端给你的JSESSIONID是共同享有的,就是你使用这个软件去访问后端接口用的一个JSESSIONID,也就是一个Session。而用户信息(也就是登录缓存信息)它是放在这个session里面的,每次调用SecurityUtils.getSubject()方法,它会去当前所处线程获取用户信息,组装subject返回。如果是没有登录过的,他就会为当前访问的JSESSIONID创建一个subject,只是这个的就是未登录状态。

排查过程

环境搭建

        由于本次仅研究调用SecurityUtils.getSubject()如何获取到正确的subject(用户)。相关的realm配置自行参考我上一章或者其他博客搭建,注意这里我是使用的shiro+jwt方式,其实其他方式shiro获取subject的内容也是一样的,只是认证方式不同,所以我这里去认证接口是会带上一个jwt值(header里面的Authorization)。

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--springboot对shiro集成-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>

 我的登录接口

        @PostMapping("/login")
        public Result login(@Validated @RequestBody UserLogin userLogin,
                            HttpServletRequest request,
                            HttpServletResponse response){
                Subject subject = SecurityUtils.getSubject();
                if(subject.isAuthenticated()){
                        subject.logout();
                }
                //登录操作,获得jwt
                String jwt = userService.login(userLogin);
                userService.setAuthorization(response,jwt);     //将jwt放在header里面

                userService.recordLogin();
                return Result.success();
        }

排查

从ThreadContext获取subject

        首先我们去看SecurityUtils.getSubject()的源码,里面的第一行就是:

Subject subject = ThreadContext.getSubject();

        我们能发现shiro是维护了一个线程上下文的ThreadContext,在里面获取subject。那么ThreadContext的subject是从哪儿来的呢?

        这里的SUBJECT_KEY是一个固定的值,用来标识线程上下文中当前的subject。

public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";

        我们发现最后这个subject是从ThreadContext维护的resources中获取的map集合,再使用key:SUBJECT_KEY的值去map集合中获取subject

        get的代码:其实就是获取当前线程的map集合(不懂的可以去了解下ThreadLocal)

        最后我们知道了subject的获取首先会去当前的线程上下文(ThreadContext)中获取,而最后都是指向ThreadContext维护的resources中。但是resources里面的subject又是谁去设置的呢。

resources中的subject如何产生

        我们在ThreadContext中查找resouces相关方法,发现有一个put方法。于是我们可以在这里面打上断点,查看到底是谁在执行put方法。

        我们按照执行栈往回找,查看有用的信息,我发现这里有一个

Subject subject = this.createSubject(request, response);

        以及方法上面有两个方法对servletRequest, servletResponse包装,其中servletRequest被封装成SHiroHttpServletRequest返回,其实这里就是对servletRequest绑定了线程上下文(ThreadLocal)。

我们的重心回到subject创建,我们发现这里createSubject是将manager以及request,response都获取去创建

这个Builder的构造方法去设置了request,response。其实他就是将这两个值绑定到了当前用户上下文中,方便后续使用

重点:现在到获取的重点了,这里依次去调用,最后我们发现生产subject的核心就在这个createSubject中

        这里它首先去拷贝了上下文中的内容,哎?这里干嘛要拷贝一份啊?不能直接用么。当然不能啊!

你看拷贝后面依次执行的是什么嘛:

this.ensureSecurityManager(context)去查看context中的SecurityManager有没有设置,如果没有就重新设置默认SecurityManager返回。

context = this.resolveSession(context);去解析session,将session放入对象上下文context中。注意这里就是从我们之前设置的request中获取session,session里面就是有登录信息的哦(不知道session是啥的伙伴自行查找)。这一步就相当于是把request的session绑定到对象上下文中。
context = this.resolvePrincipals(context);就是获取登录信息,放入contxet中。从哪儿获取呢?他会先去看如果你是认证过的,我就直接从认证信息中拿。如果没有,我就去看看当前的subject中有没有,最后都没有我就去看看session里面存不存在
Subject subject = this.doCreateSubject(context);然后就根据这些对象上下文信息去创建subject。
this.save(subject);最后save保存一下。啊?保存啥啊?其实他就是去将subject放入整个shiro维护的session中(也包括什么合并登录信息,登录状态之类,其实就是保证数据一致),不然你创建了谁能获取到啊。

        回到之前,我们为什么需要拷贝啊?我的理解是因为在创建subject的过程中随时会发生错误,在创建成功前需要保留之前的subject来表明正确的当前用户。

        到此subject就算是创建完成了。最后将subject放入resources就算完成了。

        觉得文章写得有问题,欢迎指正!

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值