6_springboot_shiro_jwt_多端认证鉴权_过滤器链

news2024/11/26 16:57:31

1. Shiro过滤器链

​ Shiro中可以配置多个Filter,那么Shiro是如何管理这些过滤器的?主要是靠ShiroFilterFactoryBean 它是一个Spring Bean,用于在Spring应用中配置Shiro的Web过滤器链。过滤器链是一系列按照特定顺序排列的过滤器,每个过滤器负责处理特定类型的请求。通过配置 ShiroFilterFactoryBean,我们可以定义哪些URL路径应被哪些过滤器处理,以及过滤器之间的执行顺序。过滤器链的配置通常以Map的形式提供,其中键是URL模式,值是对应的过滤器名称或过滤器链定义。

2. ShiroFilterFactoryBean

shiro-spring-boot-web-starter 中有自动配置,可以找到 ShiroWebFilterConfiguration 类,其中有一个bean的定义:

@Bean
@ConditionalOnMissingBean
@Override
protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
    return super.shiroFilterFactoryBean();
}
// 创建ShiroFilterFactoryBean对象
protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setShiroFilterConfiguration(shiroFilterConfiguration());
        filterFactoryBean.setGlobalFilters(globalFilters());
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

        if (filterMap != null) {
            filterFactoryBean.setFilters(filterMap);
        }

        return filterFactoryBean;
    }

实例化ShiroFilterFactoryBean 的时候,设置了登录url,登录成功后的url,从spirng容器中取出了 securityManager,还中取出过滤器的配置(一个Map对象)。

那么它到底是干什么的?
在这里插入图片描述
可以看到它实现了Spring框架的FactoryBean和BeanPostProcessor接口。

2.1 实现 FactoryBean接口

实现Spring的FactoryBean接口,意味着Spring框架会调用其中的getObject() 方法来获取bean的实例对象。这个方法实现的时候,最终调用了:createInstance 方法:

protected AbstractShiroFilter createInstance() throws Exception {
				// 如果没有获取到securityManager则抛出异常
        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }
				// 创建过滤器链管理器
        FilterChainManager manager = createFilterChainManager();
        // 过滤器链路径解析
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

       	// 实例化 SpringShiroFilter 并注入了SecurityManager,DefaultFilterChainManager以及PathMatchingFilterChainResolver,完成了SpringShiroFilter的单例构造
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver, getShiroFilterConfiguration());
    }

2.2 创建过滤器链管理器

在上面的 createInstance() 方法中(第14行),创建了FilterChainManager:

protected FilterChainManager createFilterChainManager() {
		// 在它的构造方法中,加入了所有的默认的过滤器
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
       	...
        return manager;
}

2.3 实现BeanPostProcessor接口

BeanPostProcessor 接口也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法

  • postProcessBeforeInitialization(Object bean,String beanName)

    会在每一个bean对象的初始化方法调用之前回调。 在ShiroFilterFactoryBean的实现中,通过拦截Filter类型的bean,完成全局属性的注入,包括设置:loginUrl、successUrl、unauthorizedUrl;

  • postProcessAfterInitialization(Object bean,String beanName)

    会在每个bean对象的初始化方法调用之后被回调。在ShiroFilterFactoryBean的实现中,它直接返回了bean,没有做任何处理。

3. SpringShiroFilter

ShiroFilterFactoryBean 创建的实例Bean实例对象就是 SpringShiroFilter ,它的继承关系:
在这里插入图片描述

它是一个javax.servlet.Filter的实现,那它是如何注册到Servlet容器中的呢?一般我们在SpringMVC应用中,注册一个Servlet Filter可以借助于FilterRegistrationBean 来实现,它实现了ServletContextInitializer 这个接口。容器在启动的时候,会将所有实现了 ServletContextInitializer接口的bean注册到Spring容器中。

3.1 注册Filter

shiro-spring-boot-web-starter 中有自动配置,可以找到 ShiroWebFilterConfiguration 类,其中就定义了FilterRegistrationBean 这个bean:

@Bean(name = REGISTRATION_BEAN_NAME)
@ConditionalOnMissingBean(name = REGISTRATION_BEAN_NAME)
// 将AbstractShiroFilter(实际类型是SpringShiroFilter)注册到Servlet容器中
protected FilterRegistrationBean<AbstractShiroFilter> filterShiroFilterRegistrationBean() throws Exception {

    FilterRegistrationBean<AbstractShiroFilter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD,
                                              DispatcherType.INCLUDE, DispatcherType.ERROR);
    filterRegistrationBean.setFilter((AbstractShiroFilter) shiroFilterFactoryBean().getObject());//这里获取到的就是SpringShiroFilter
    filterRegistrationBean.setName(FILTER_NAME);
    filterRegistrationBean.setOrder(1);//数字越小,越优先

    return filterRegistrationBean;
}

在前面的案例中,我们还自定义了AuthenticationFilter 并使用 FilterRegistrationBean 注册到了Servlet 容器中了。因为 SpringShiroFilter 的order设置为了1,所以所有的请求必先经过它, 也就是说,请求需要穿过两个filter,第一个是 SpringShiroFilter,第二个才是我们定义的filter

3.2 执行流程分析

  1. OncePerRequestFilter::doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

    标记该filter是否处理过请求。处理过则继续执行过滤器链上的其它filter. 它的功能和类的名字一样,只处理一次请求。

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        	...
                doFilterInternal(request, response, filterChain);
            ...
        }
    }
    

    doFilterInternal 方法被定义为抽象方法,它被子类(AbstractShiroFilter)实现,是真正的处理过滤器逻辑方法

  2. AbstractShiroFilter::doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)

    在这个类中出现了WebSecurityManager ,它的子类SpringShiroFilter 在ShiroFilterFactoryBean 中被创建的时候,WebSecurityManager 对象被set进来了。

     protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
                throws ServletException, IOException {
    			...
                final Subject subject = createSubject(request, response);
                subject.execute((Callable<Void>) () -> {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                });
         	   ...
    }
    

    可以看到,这里创建了Subject, 并且去更新session的最后访问时间,然后就将请求交给了下一个过滤器了。

3.3 小结

shiro中默认有一个过滤器SpringShiroFilter, 它被实例化的时候需要被“注入” SecurityManager ,需要被放入到Spring 容器和 注册到Servlet容器中。这个过程相对比较复杂,就使用了 ShiroFilterFactoryBean 来进行创建

当请求到来时,它是第一个经过的过滤器,它的主要任务就是创建出Subject 对象,并更新session的最后访问时间,然后请求就被交给了其它过滤器了。

4. Subject对象的创建

跟踪createSubject(request,response) :

public abstract class AbstractShiroFilter extends OncePerRequestFilter {
	...
    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
            return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }

    ...

    public WebSubject buildWebSubject() {
        Subject subject = super.buildSubject();
        ...
        return (WebSubject) subject;
    }

    ...

    // 可以看到 subject对象的创建,交给了securityManager来完成。
    public Subject buildSubject() {
        return this.securityManager.createSubject(this.subjectContext);
    }
}

在交给securityManager创建subject之前,AbstractShiroFilter 中准备了一个 SubjectContext 实际类型是 DefaultSubjectContext这一点可以在 Subject.Builder来中的newSubjectContextInstance 方法中找到。
在这里插入图片描述
它实际上是一个java.util.Map,它就是用来保存当前subject中的数据的,下面是map中的key:

  • SECURITY_MANAGER (securityManager对象)
  • SESSION_ID (sessionId)
  • SUBJECT(subject)
  • PRINCIPALS 身份信息
  • SESSION 会话
  • AUTHENTICATED 是否认证
  • AUTHENTICATION_INFO (reaml 中的 AuthenticationInfo,即认证信息)
  • AUTHENTICATION_TOKEN (提交的认证token信息)

这个对象刚被创建出来的时候,里面的数据是空的。但是随着调用链的深入,这些信息将会被逐步填充进去

真正创建Subject对象是由SecurityManager来完成的:

public class DefaultSecurityManager extends SessionsSecurityManager {
    ...
    public Subject createSubject(SubjectContext subjectContext) {
            SubjectContext context = copy(subjectContext);
            // securityManager对象填充到 subjectContext (它是一个Map)中
            context = ensureSecurityManager(context);
            // 将session填充到subjectContext中。
            // 跟踪这个方法,最终会到达 sessionManager.getSession(Session key) ,此时在sessionManager中,就可以从请求中获取sessionID或者从保存的session中获取session对象。
            context = resolveSession(context);
           // 身份信息
            context = resolvePrincipals(context);
            /// subject对象
            Subject subject = doCreateSubject(context);
            save(subject);
            return subject;
        }
    ...
}

跟踪源码发现,实际被创建出来的 Subject对象是 :WebDelegatingSubject ,初始化的数据都是从 subjectContext中取出来的,但到目前为止,SubjectContext中仅仅只有 securityManager.

resolveSession(context) 这个方法最终会调用 SessionManager的 Session getSession(SessionKey key) 方法来获取session

5. 绑定subject对象到当前线程

过滤器中创建了 Subject对象以后,紧接着执行execute方法,

public abstract class AbstractShiroFilter extends OncePerRequestFilter { 
    ...
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
                throws ServletException, IOException {
                ...
                final Subject subject = createSubject(request, response);
        		//使用subject.execute来执行代码,保证线程与数据的绑定与解绑
                subject.execute((Callable<Void>) () -> {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                });
               ...
    }
}

Subjet会被绑定到 ThreadContext 中, ThreadContext封装了 ThreadLocal,实际使用的是java.lang.InheritableThreadLocal 所以不管是在父线程还是子线程中,调用 SecurityUtils.getSubject() 都能获取到当前用户的信息。

6. 经过其它过滤器

在经过其它过滤器的时候,因为前面已经将Subject对象绑定到了当前线程中,所以其它地方获取的都是同一个Subject对象。

7. 自己配置ShiroFilterFactoryBean

前面我们自己定义的AuthenticationFilter,利用了 FilterRegistrationBean,将其注册到了Servlet 容器中了。

public class AuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
    ...
}

经过了上面的分析,我们只需要注册一个SpringShiroFilter到Servlet容器即可,其它的Shiro中用到的自定义过滤器,我们只需要将他们放入到 过滤器管理器(DefaultFilterChainManager)中即可。

按照这个思路,将项目中的 com.qinyeit.shirojwt.demos.configuration.ShiroConfiguration 改成如下配置:

@Configuration
@Slf4j
public class ShiroConfiguration {
    ...
     /**
     * 自定义拦截器
     *
     * @return
     */
    private Map<String, Filter> getCustomerShiroFilter() {
        AuthenticationFilter authcFilter = new AuthenticationFilter();
        // 设置登录请求的URL
        authcFilter.setLoginUrl("/login");
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc", authcFilter);
        return filters;
    }  
    /**
     * URL配置
     *
     * @return
     */
    private ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }
    /**
     * 重要配置
     * ShiroFilter 的 FactoryBean
     *
     * @param securityManager
     * @return
     */
    @Bean
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
        filterFactoryBean.setFilters(getCustomerShiroFilter());
        return filterFactoryBean;
    }
    ...
}

代码仓库 https://github.com/kaiwill/shiro-jwt , 本节代码在 6_springboot_shiro_jwt_多端认证鉴权_过滤器链 分支上.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1554128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于Devc++调试的问题以及解决STL变量无法查看

目前Devc的调试主要有以下几点&#xff1a; 1.调试不能直接查看stl变量&#xff0c;会卡死不动 2.目前单步进入只能用鼠标键按 3.若想按下一步进入函数体内&#xff0c;要在函数体内打上断点才行 4.调试到return 0 ;上一句就停了&#xff0c;不会结束程序 5.目前F2跳至断点…

代码随想录训练营Day35:● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 题目链接 https://leetcode.cn/problems/lemonade-change/description/ 题目描述 思路 class Solution {public boolean lemonadeChange(int[] bills) {int five 0,ten 0,twenty 0;for (int i 0; i < bills.length; i) {if(bills[i] 5){five;}else i…

记录在项目中引用本地的npm包

1、先把需要的包下载下来&#xff0c;以Photo Sphere Viewer 为引用的npm包、项目以shpereRepo为例子 git clone https://github.com/mistic100/Photo-Sphere-Viewer2、拉下代码后修改之后执行 ./build.sh build.sh #!/usr/bin/env bashyarn run build targetDir"../sh…

SpringBoot 登录认证(二)

HTTP是无状态协议 HTTP协议是无状态协议。什么又是无状态的协议&#xff1f; 所谓无状态&#xff0c;指的是每一次请求都是独立的&#xff0c;下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互&#xff0c;基于HTTP协议也就意味着现在我们通过浏览器来访…

jmeter中参数加密

加密接口常用的方式有&#xff1a; MD5&#xff0c;SHA&#xff0c;HmacSHA RSA AES&#xff0c;DES&#xff0c;Base64 压测中有些参数需要进行加密&#xff0c;加密方式已接口文档为主。 MD5加密 比如MD5加密的接口文档&#xff1a; 请求URL&#xff1a;http://101.34.221…

Unity AI Navigation自动寻路

目录 前言一、Unity中AI Navigation是什么&#xff1f;二、使用步骤1.安装AI Navigation2.创建模型和材质3.编写向目标移动的脚本4.NavMeshLink桥接组件5.NavMeshObstacle组件6.NavMeshModifler组件 三、效果总结 前言 Unity是一款强大的游戏开发引擎&#xff0c;而人工智能&a…

基于单片机病房呼叫系统数码管显示房号设计

**单片机设计介绍&#xff0c;基于单片机病房呼叫系统数码管显示房号设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机病房呼叫系统数码管显示房号设计概要主要涵盖了利用单片机技术实现病房呼叫系统&#xff0c;并…

基于springboot实现校园周边美食探索及分享平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现园周边美食探索及分享平台系统演示 摘要 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的…

【SpringCloud】Ribbon负载均衡

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

新家装修选中央空调如何选?认准约克VRF中央空调

在现代家居生活中,追求舒适和健康生活环境的家庭越来越倾向于选择中央空调系统。面对市场上琳琅满目的中央空调品牌,如何挑选一款合适的家用中央空调成为许多消费者的一大难题。今天,我们以约克VRF中央空调为例,深入探讨其特点和优势,为广大家庭提供一个舒适的选择答案。 首先…

玩美移动升级虚拟试妆体验: 推出3D多色腮红AR试妆解决方案

领先的AI和AR美妆和时尚技术解决方案供应商&#xff0c;以及“美力AI”解决方案开发商——玩美移动于今天宣布&#xff0c;其3D腮红虚拟试妆工具已实现进一步技术提升。用户可以通过三色腮红虚拟试妆体验&#xff0c;尝试更多腮红色号、质地以及多种颜色组合妆效。本次虚拟试妆…

应用方案 D431L可调精密基准源,可耐压35V以上

概述 D431L是一种低压三端可调稳压器&#xff0c;保证在适用温度范围内的热稳定性。输出电压可以设置为VREF(约1.24V)~16V&#xff08;接两个外部电阻&#xff09;。该装置具有典型的动态输出0.2Ω的阻抗。在很多应用中&#xff0c;可替代齐纳二极管。 D431L有TO-92和SOT23封装…

Go打造REST Server【二】:用路由的三方库来实现

前言 在之前的文章中&#xff0c;我们用Go的标准库来实现了服务器&#xff0c;JSON渲染重构为辅助函数&#xff0c;使特定的路由处理程序相当简洁。 我们剩下的问题是路径路由逻辑&#xff0c;这是所有编写无依赖HTTP服务器的人都会遇到的问题&#xff0c;除非服务器只处理一到…

用xshell或ftp连接本地虚拟机linux系统,centos7修改动态ip地址

如果不知道怎么下载vm本地虚拟机软件或者不知道怎么安装可以参考我上一篇博客 vmWare虚拟机下载安装详细教程,手把手一步一步教学-CSDN博客 安装好虚拟机软件我们想要通过xshell和ftp工具来管理,小黑框不太舒服哈哈哈 一.准备工作 输入命令来查看当前的ip地址 ip addr 可以…

unity学习(76)--窗口化和后台运行

1.通过如下方式将编译的游戏设置为窗口模式。 成功&#xff1a; 2.现在只有鼠标点击的窗体游戏运动&#xff0c;其他窗体游戏都会卡住。 2.1build setting中 2.2unity内部Project Settings 也被同步修改了

VBA技术资料MF135:多值匹配查找

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

unity 数据的可视化

【Unity 实用插件篇】| 可视化图表插件XCharts (折线图、柱状图、饼图等)详细教学-腾讯云开发者社区-腾讯云 Package https://github.com/XCharts-Team/XCharts/releases 官方文档案例 入门教程&#xff1a;5分钟上手 XCharts 3.0 | XCharts (xcharts-team.github.io)

idea2023+jdk1.8+Maven3.6.3+Testng6.10+junit4.13搭建测试

idea2023jdk1.8Maven3.6.3Testng6.10junit4.13搭建测试 首先创建maven工程 导入依赖 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/…

文心一言 VS 讯飞星火 VS chatgpt (225)-- 算法导论16.3 7题

七、推广赫夫曼算法&#xff0c;使之能生成三进制的码字(即码字由符号0、1、2组成)&#xff0c;并证明你的算法能生成最优三进制码。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 推广赫夫曼算法以生成三进制码字需要对算法进行一定的修改&#xff0c;确保在每…

前端学习<二>CSS基础——08-CSS属性:定位属性

CSS的定位属性有三种&#xff0c;分别是绝对定位、相对定位、固定定位。 position: absolute; <!-- 绝对定位 -->​position: relative; <!-- 相对定位 -->​position: fixed; <!-- 固定定位 -->​ 下面逐一介绍。 相对定位 相对定位&#xff1a;让…