shiro中subject创建,以及shiro如何保证用户登录状态

文章详细分析了Shiro中Subject的创建过程,特别是在SpringBoot项目中的运用。Subject作为主体,保存用户登录状态,其认证状态由authenticated字段标识。当新的请求到来时,Shiro通过session管理来判断用户是否已认证,确保登录状态的持久性。整个认证流程涉及SecurityUtils、UsernamePasswordToken、Realm的认证以及session的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一篇弄懂shiro中subject创建、用户登录状态保存、以及整个认证流程的分析与总结


  • 声明:本文适用于已经基本掌握使用shiro做javaee/se项目安全框架但对于shiro运行原理还有些迷糊的初学者(附上源码地址方便读者下载测试:https://gitee.com/ZMXwan/shiro-testing-project.git)。

  • 重要概念什么是subject:subject是一个主体,用于保存一个用户或者是一个对象,指代和当前应用交互的任何对象。它更像是一个门面,只要是关于认证和授权的操作都需要委托它去完成。

  • 提出问题

    • 问题背景:在一个springboot项目中整合了shiro做认证授权,由于srpingboot的web-starter中内嵌了tomcat,而tomcat是使用线程池去处理浏览器发来的每一个请求。

      已知条件:假如这是用户第一次登陆,访问对应login接口。这时,当前线程使用SecurityUtils.getSubject方法会获取shrio自动构建的subject对象(同一线程在任何地方获取到的的subject对象是一致的),并使用subject.login方法进行身份认证—实际上就是更改subject对象中的authenticated字段值为true。

      问题:那么对于下一次请求,又是一个新的线程去处理这个请求,并产生新的subject对象,那么shiro如何判断当前的这个请求(或者说是这个新的subject对象)是不是已经认证过?即—shiro如何保证用户登录状态?。

    login接口示例:
    @RequestMapping("/login")
        public Result<?> toLogin(User userInfo) {
        	//1、获取当前subject对象
            Subject subject = SecurityUtils.getSubject();
        	//2、构建身份令牌
            UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUserName(),userInfo.getUserPwd());
            if(token !=null)
                //3、使用令牌登录(其实就是走Realm中的认证流程)
                subject.login(token);
            return new Result<>().success().put(userService.getUserByName(userInfo.getUserName()));
    	}
    

回答:这个问题实际上要了解shiro的认证流程与subject的创建过程。

手把手解题步骤:

  • 假设你已经有了如下一个简单的项目(也可以使用本文提供的源码:https://gitee.com/ZMXwan/shiro-testing-project.git):

    ​ 1、这个项目是一个sringboot项目并整合了shiro。

    ​ 2、这个项目中编写了一个简单的用户登录业务,并用shiro做用户认证。

  • 开始解题:

    • 1)在登录接口处打上断点

      在这里插入图片描述

    • 2)右键debug运行

      在这里插入图片描述

    • 3)postman发送请求:http://localhost:8080/user/login?userName=admin&userPwd=123

    • 4)查看idea下的方法调用栈,这是我们开始分析的第一个重点。

      • 4.1)不出意外的话的话方法调用栈会停在如笔者类似的登录接口的断点处,上面会显示此时线程执行的方法所在的:

        ​ 类的名字(UserController)、方法名(toLogin)和包名(com.adminsys.user.controller)。

      在这里插入图片描述

      • 4.2)我们接着向下看,由于shiro在web项目中本质上是使用拦截器filter来实现请求拦截的,所以我们可以找到第一个带有filter与shiro字眼的的类名,这样直接从头去读执行链。

      在这里插入图片描述

      • 4.3)我们找到了第一个类下的方法:OncePerRequestFilter下的doFilter(),如图所示,并没有与subject关联的关键代码,所以我们直接跳过。

        在这里插入图片描述

        注意:为了节约时间,接下来的分析中我将跳过一些类以及其方法,仅仅保留一些关键代码。读者若有兴趣,笔者还是推荐一步一步的自己看一遍。

      • 4.4)接着我们查看AbstractShiroFilter中的doFilterInternal方法,这时我们第一次看见了与subject的创建相关的关键方法。

      在这里插入图片描述

      • 4.5)我们进入这个create Subject方法:

        • 接下来开始就比较绕了,如果读者熟悉建造者设计模式或者内部类的话读起来会轻松一点,但是如果不懂的话也没关系,可以跟着笔者慢慢读。

        • 首先思考,这个方法返回的是一个WebSubject对象,而这个对象最终是使用.bulidWebSubject()方法构建出来的。那我们就先看看这个bulidWebSubject()方法。

      在这里插入图片描述

      • 4.6)我们进入.bulidWebSubject()方法

        • 1)可以看见,实际上这个.bulidWebSubject()方法是使用WebSubject类的父类构造器进行subject对象中的内部类创建的。

        在这里插入图片描述

        • 2)而这个父类其实就是Subject.Bulider类(也叫Builder,且是WebSubject类下Builder的父类,这其实是使用内部类实现多继承的一种方式)。

        在这里插入图片描述

        • 3)在Subject.Builder类下使用Subject类中的两个成员变量来构建Subject对象;this.securityManager.createSubject(this.subjectContext)

          在这里插入图片描述

          那么这两个对象在何时创建,其作用是什么?

          ​ 1、securityManager,是在容器启动时通过我们自定义的shiro配置类进行配置,并使用ShiroFilterFactoryBean对象创建SpringShiroFilter(即AbstractShiroFilter的子类)时将securityManager注入。

          在这里插入图片描述

          ​ 作用:指定自定义的安全策略,即在配置类中绑定我们自定义的Realm子类

          ​ 2、subjectContext,是通过WebSubject.Builder重写的Subject.Builder类下的方法newSubjectContextInstance返回的DefaultWebSubjectContext类对象。

          在这里插入图片描述
          在这里插入图片描述

          ​ 作用:保存请求的上下文信息,包括request信息、response信息以及security管理信息

          在这里插入图片描述

        • 3)这两个对象含义弄明白后,我们将利用这两个对象进入真正创建环节。首先,我们先找到2)下第一张图片下的createSubject方法.

          在这里插入图片描述

          我们想要正确的进入这个createSubject方法的要知道this.securityManager到底是个什么对象,只要查看当前this对象即可。

          在这里插入图片描述

          在这里插入图片描述

          进入DefaultWebSubjectFactory中

          在这里插入图片描述

          找到createSubject,这是最后的创建过程,其中注意authenticed,它实际上在第一次登陆时默认值为false。因为此时我们还没有认证,在后面创建subject时是会把这个字段值注入到subject对象中。最后当前线程会使用ThreadLocal保存这个subject对象从而保证当前线程未结束的情况下,从代码任何地方通过SecurityUtils获取的subject对象都是通一个对象。而这个authenticed实际上还会保存在servlet的session域中,shiro支持三种会话管理(DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;

          ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

          DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理)

          在这里插入图片描述

          这里我们使用的是Servlet容器的会话(验证也很简单,可以在yml文件中设置会话的过期时间进行检验server.servlet.session.timeout=1800 # 单位为秒,这里设置Session的过期时间为30分钟).之后再进入自定义的认证流程并将该字段值置为true,并返回数据。

          在这里插入图片描述

          假设当新一次的请求到达时,且此时浏览器与服务器的session未到期,那么新创建的subject会从当前会话中获取authenticed字段值,并赋给subject对象跳过认证过程从而保证用户登陆状态。

          在这里插入图片描述

          postman请求http://localhost:8080/user/list?deleted=0 授权成功

          在这里插入图片描述


总结

  • 1)对于浏览器发起的每一个请求,shiro都会生成一个全新的subject。
  • 2)subject的用户状态通过对象中的authenticed判断,值为true代表当前请求对应的subject是认证过的。
  • 3)authenticed字段值同时还保存在session域中,这使得同一浏览器发出的请求都能共享登录状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值