sentinel黑白名单权限控制

黑白名单权限控制

规则配置

规则创建

  1. 创建一个 AuthorityRule 规则对象
  2. 三个关键要素
    • setStrategy: 黑白名单类型
    • setResource: 规则和资源的绑定关系
    • setLimitApp: 限制的来源
  3. 调用 AuthorityRuleManager.loadRules()加载规则

监听器实例化和管理

AuthorityPropertyListener 监听器来感知黑白名单规则的变化, 将此监听器放入 SentinelProperty 中进行管理

现有疑惑

  1. 没有看到创建监听器AuthorityPropertyListener的地方
  2. 没有看到将监听器添加到监听器管理者的地方, 即调用 SentinelProperty#addListener 方法
  3. 只看到了一句 AuthorityRuleManager.loadRules()

猜测是否创建监听器将监听器添加到监听器管理者两个动作都在AuthorityRuleManager.loadRules()

查验代码发现确实如此

public final class AuthorityRuleManager {
    // 其它代码...

    // 创建监听器动作
    private static final RulePropertyListener LISTENER = new RulePropertyListener();
    
    // 将监听器添加到监听器管理者
    static {
        // 将黑白名单 Listener 放到 SentinelProperty 当中去管理
        currentProperty.addListener(LISTENER);
    }

    // 其它代码...
}

具体详细代码如下

public final class AuthorityRuleManager {
    // 资源名称 -> 资源对应的规则
    private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();

    // 饿汉式单例模式实例化黑白名单 Listener 对象
    private static final RulePropertyListener LISTENER = new RulePropertyListener();
    // Listener对象的管理者
    private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();

    static {
        // 将黑白名单 Listener 放到 SentinelProperty 当中去管理
        currentProperty.addListener(LISTENER);
    }
    
    // 静态内部类的方式实现 黑白名单Listener
    private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {
        // 规则初始化
        @Override
        public synchronized void configLoad(List<AuthorityRule> value) {}
        // 规则变更
        @Override
        public synchronized void configUpdate(List<AuthorityRule> conf) {}
    }
}

初始化规则

####初始化规则位置

上述代码已经实例化了黑白名单监听器,并且已经将监听器交由 SentinelProperty 进行管理, 我们知道监听器监听的是规则, 那么还需要初始化规则

通常情况下,在调用 currentProperty.addListener(LISTENER) 之后,我们会再执行一条初始化规则的代码.

但是sentinel没有这么做, 为什么? 因为没必要, 看下述案例, 发现本质都是一样的, 换汤不换药罢了

// 方式一: 调用addListener后, 再调用初始化规则代码
static {
    // 将监听器交给SentinelProperty管理, 这里的addListener只有添加监听器逻辑
    currentProperty.addListener(LISTENER);
    // 初始化规则
    listener.configLoad(value)
}

addListener(...) {
    // 添加监听器
    listeners.add(listener);
}

// ------------------

// 方式二: 将初始化规则代码合并到addListener中
static {
    // 将监听器交给SentinelProperty管理, 里边方法 
    currentProperty.addListener(LISTENER);
}

addListener(...) {
    // 添加监听器
    listeners.add(listener);
    // 初始化规则
    listener.configLoad(value);
}

sentinnel真正的做法如下, 将初始化规则动作合并到addListener(), 只要调用 addListener() 方法就会进行规则的初始化, 具体的方法实现如下

public class DynamicSentinelProperty<T> implements SentinelProperty<T> {

    protected Set<PropertyListener<T>> listeners = new CopyOnWriteArraySet<>();
    
    private T value = null;
    
    @Override
    public void addListener(PropertyListener<T> listener) {
        listeners.add(listener);
        // 调用黑白名单的初始化规则方法
        listener.configLoad(value);
    }
}

此时黑名单规则初始化的流程就明朗了, 如下图所示

  • AuthorityRuleManager初始化时, 调用addListener()
    • 注册监听器
    • 初始化规则
      在这里插入图片描述
初始化规则逻辑configLoad()

DynamicSentinelProperty#addListener()中的configLoad()实际上调用的是AuthorityRuleManager.RulePropertyListener#configLoad(), 也就是下边这块代码

public final class AuthorityRuleManager {
    // 资源名称 -> 资源对应的规则
    private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();

    // 省略上面代码...

    // 静态内部类的方式实现 黑白名单Listener
    private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {
        // 规则初始化
        @Override
        public synchronized void configLoad(List<AuthorityRule> value) {
            authorityRules.updateRules(loadAuthorityConf(value));

            RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);
        }
        // 规则变更
        @Override
        public synchronized void configUpdate(List<AuthorityRule> conf) {
            authorityRules.updateRules(loadAuthorityConf(conf));

            RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);
        }

        // 加载规则, 这里将资源和资源对应的规则列表放到Map中管理
        private Map<String, List<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
            Map<String, List<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();
			
            if (list == null || list.isEmpty()) {
                return newRuleMap;
            }

            // 遍历每个规则
            for (AuthorityRule rule : list) {
                if (!isValidRule(rule)) {
                    RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule);
                    continue;
                }

                if (StringUtil.isBlank(rule.getLimitApp())) {
                    rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
                }
				
                // 获取规则对应的资源名称
                String identity = rule.getResource();
                List<AuthorityRule> ruleSet = newRuleMap.get(identity);
                // 将规则放到 Map 当中
                if (ruleSet == null) {
                    ruleSet = new ArrayList<>();
                    ruleSet.add(rule);
                    newRuleMap.put(identity, ruleSet);
                } else {
                    // 一个资源最多只能有一个权限规则,所以忽略多余的规则即可
                    RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString());
                }
            }

            return newRuleMap;
        }
    }
}

我们又知道手动初始化规则的代码是AuthorityRuleManager.loadRules(ruleList), 其实调用

public final class AuthorityRuleManager {
    // 发现currentProperty其实指向的就是DynamicSentinelProperty, 即上边分析的
    private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();

    // 初始化调用的就是这个
    public static void loadRules(List<AuthorityRule> rules) {
        // 调用监听器的 updateValue() 方法来通知每一个监听者的 configUpdate() 方法
        currentProperty.updateValue(rules);
    }
}


public class DynamicSentinelProperty<T> implements SentinelProperty<T> {
    // 省略其它代码...

    @Override
    public boolean updateValue(T newValue) {
        if (isEqual(value, newValue)) {
            return false;
        }
        RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);
		
        // 将传入的规则赋值给value
        value = newValue;
        // 遍历通知所有监听者
        for (PropertyListener<T> listener : listeners) {
            // 这里调用了configUpdate, 即上边分析的configUpdate()
            // 具体全类名如下com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager.RulePropertyListener#configUpdate
            listener.configUpdate(newValue);
        }
        return true;
    }
}

大家可能会产生一个疑问:静态代码块里不是已经将规则初始化完成了吗?为什么这里调用 loadRules() 方法调用 updateValue() 来通知监听者说规则变更了呢

因为执行静态代码块里的 listener.configLoad(value)时, 这里的全局变量value初始默认为null, 首次调用 listener.configLoad(value) 进行规则初始化是不会成功的, 所以这里又调用loadRules(), 将规则集合参数携带过去, 最终才能正常进入 for 循环遍历规则集合,将其组装成 Map 结构

如下图所示
在这里插入图片描述

到此为止, 规则已经初始化完成且将资源和规则的映射关系放到了Map中存储, 接下来就是对规则的校验

规则验证

黑白名单规则验证是我们责任链中的第五个Slot, 负责校验黑白名单

上边初始化得到一个资源和规则的映射关系的Map, 那么这里来就可以遍历这个map验证是否有访问权限

public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
        throws Throwable {
        // 规则校验
        checkBlackWhiteAuthority(resourceWrapper, context);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }

    void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
		// 通过AuthorityRuleManager获取获取当前资源的规则集合
        List<AuthorityRule> rules = AuthorityRuleManager.getRules(resource.getName());
        if (rules == null) {
            return;
        }
		
        // 遍历规则
        for (AuthorityRule rule : rules) {
            // passCheck进行校验, 如果不通过就抛出AuthorityException
            if (!AuthorityRuleChecker.passCheck(rule, context)) {
                throw new AuthorityException(context.getOrigin(), rule);
            }
        }
    }
}

可以看到核心就是AuthorityRuleChecker.passCheck(), 下边分析一下

final class AuthorityRuleChecker {

    static boolean passCheck(AuthorityRule rule, Context context) {
        // 获取origin
        String requester = context.getOrigin();

        // 如果没设置来源,或者没限制app,那直接放行就好了,相当于不做规则限制
        if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
            return true;
        }

        // 判断此次请求的来源是不是在limitApp里,注意这里用的是近似精确匹配,但不是绝对精确,
        // 比如limitApp写的是a,b。然后资源名称假设是",b",那么就出问题了,因为limitApp是按照逗号隔开的,但是资源却包含了逗号
        // 这样的话下面算法就是 contain=true,这显然是不对的
        int pos = rule.getLimitApp().indexOf(requester);
        // 这里判断是都大于-1, 进而得到limitapp是否包含origin
        boolean contain = pos > -1;
		
        // 如果近似精确匹配成功的话,在进行精确匹配
        if (contain) {
            boolean exactlyMatch = false;
            // 使用英文逗号进行切割limitapp(可以设置多个limitapp,之间是用逗号分隔的)
            String[] appArray = rule.getLimitApp().split(",");
            for (String app : appArray) {
                if (requester.equals(app)) {
                    exactlyMatch = true;
                    break;
                }
            }

            contain = exactlyMatch;
        }

        int strategy = rule.getStrategy();
        // 如果是黑名单,并且此次请求的来源在limitApp里
        if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
            // 返回false, 表示限流
            return false;
        }

        // 如果配置是白名单, 并且origin不在limitApp
        if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
            // 返回false, 表示限流
            return false;
        }

        
        // 执行到这里说明, 就通过了校验, 放行
        // 1. 如果是黑名单, 那么origin就不在limitApp内
        // 2. 如果是白名单, 那么origin在limitApp内
        return true;
    }

    private AuthorityRuleChecker() {}
}

验证流程图如下
在这里插入图片描述

仅当调用源不为空且规则配置了黑名单或白名单时,才会执行黑白名单的筛选逻辑。这表明,实现黑白名单限流的前提条件是,每个客户端在发起请求时都必须将自己服务唯一标志放到 Context 的 origin 里

  • context.getOrigin() 方法,因此在做黑白名单规则控制的时候,我们需要先定义好一个 origin,这个 origin 可以是userId,也可以是IP地址,还可以是项目名称等,比如我们将 userId 为 1 和 2 的用户加入黑名单,那么我们就需要在每次请求此资源时在Context的origin里添加上userId,这个实现起来也很简单,可以搞个AOP每次都从header 或其他地方获取userId, 然后放到 Context 的origin里即可

参考资料

通关 Sentinel 流量治理框架 - 编程界的小學生 )

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud Alibaba 入门简介

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第十一篇&#xff0c;即介绍 SpringCloud Alibaba 的入门信息。 二、出现的原因 Spring Cloud Netflix…

基于django的CRM客户关系管理系统的python设计与开发flask-vue

本基于django的CRM系统是根据当前客户关系相关的内容实际情况开发的&#xff0c;在系统语言选择上我们使用的python语言&#xff0c;数据库是小巧灵活的MySQL数据库&#xff0c;本系统的开发可以极大的提高基于django的CRM系统的管理效率。 本基于django的CRM系统采用python语言…

计算机网络分层模型介绍

计算机网络分层模型是一种组织网络通信的方法&#xff0c;它将复杂的网络通信任务分解为多个较小的、更易于管理的层次。每个层次负责处理特定的通信任务&#xff0c;并与上下层交互。最著名的分层模型是OSI&#xff08;开放系统互联&#xff09;模型和TCP/IP模型。下面将详细介…

Python爬虫新手村上手指南②

HTTP与HTTPS HTTP协议 全称是Hypertext Transfer Protocol 中文意思是&#xff1a;超文本传输协议&#xff0c;是一种发布和接收HTML(Hypertext Markup Language)页面的方法。 服务端口号为80端口 HTTPS协议 全称是Hypertext Transfer Protocol over Securesocket Layer …

BootScrap详细教程

文章目录 前言一、BootScrap入门二、导航三、栅格系统四、container五、面板六、媒体对象七、分页八、图标九、实现动态效果 前言 BootScrap是别人帮我们写好的CSS样式。如果想要使用BootScrap&#xff0c;需要先下载下来&#xff0c;在页面上引入&#xff0c;编写HTML需要按照…

【C语言】空心正方形图案

思路&#xff1a; 1&#xff0c;两行两列打印* &#xff1a;第一行和最后一行&#xff0c;第一列和最后一列。 2&#xff0c;其他地方打印空格。 代码如下&#xff1a; #include<stdio.h> int main() { int n 0; int i 0; int j 0; while (scanf("…

【LabVIEW FPGA入门】并行执行

利用图形化编程的并行特性以及 FPGA 上 LabVIEW 图的真正并行实现&#xff0c;您可以通过将应用程序代码划分为更小的进程来进一步优化执行速度。与整个应用程序在一个循环中运行相比&#xff0c;这使得每个进程能够实现更高的循环速率和更高的应用程序整体执行速率。 …

除了 ChatGPT,还有哪些好用的AI工具?

0. 前言 OnlyFans 订阅教程移步&#xff1a;【保姆级】2024年最新Onlyfans订阅教程 Midjourney 订阅教程移步&#xff1a; 【一看就会】五分钟完成MidJourney订阅 GPT-4.0 升级教程移步&#xff1a;五分钟开通GPT4.0 如果你需要使用Wildcard开通GPT4、Midjourney或是Onlyfa…

哪些超声波清洗机值得买?百元内超声波清洗机值得买推荐

日常生活中&#xff0c;无论是精致的珠宝首饰、眼镜&#xff0c;还是日常使用的化妆刷、剃须刀等&#xff0c;难免会沾染灰尘与污垢&#xff0c;长久下来不仅影响外观&#xff0c;更可能对使用效果造成负面影响。而传统的手工清洁往往既费时又费力&#xff0c;且难以彻底清洁到…

2024年度最佳大型语言模型(LLMs)汇总

大型语言模型(LLMs)是人工智能文本处理的主要类型&#xff0c;也现在最流行的人工智能应用形态。ChatGPT是迄今为止最著名的使用LLM的工具&#xff0c;它由OpenAI的GPT模型的特别调整版本提供动力。但还有许多其他聊天机器人和文本生成器&#xff0c;包括从Google Bard和Anthro…

机器学习_正则化

文章目录 代价函数 如果我们有非常多的特征&#xff0c;我们通过学习得到的假设可能能够非常好地适应训练集&#xff08;代价函数可能几乎为 0&#xff09;&#xff0c;但是可能会不能推广到新的数据。 下图是一个回归问题的例子&#xff1a; 第一个模型是一个线性模型&#xf…

Anaconda下载以前的旧版本

由于Anaconda新的版本&#xff0c;可能不太适合我们当前开发&#xff0c;我们需要下载历史版本 访问Anaconda官网的历史版本下载页面: https://repo.anaconda.com/archive/

PostgreSQL开发与实战(7)多版本并发控制1

作者&#xff1a;太阳 一、 表系统字段几个比较重要概念 1.1 tuple tuple表示表中的数据行&#xff0c;在MySQL中用row表示。 在表数据页中&#xff0c;主要分为普通的数据元祖和TOAST元祖。以下是一个普通数据元祖的结构&#xff0c;主要由三部分组成&#xff1a;HeapTupl…

如何进行软件安全性测试?CMA、CNAS软件安全测试报告获取

软件安全性测试是保障软件应用安全的重要手段&#xff0c;通过对软件系统的安全性进行全面评估和检测&#xff0c;以确保软件能够抵御各种潜在的安全威胁和风险。那么如何进行软件安全性测试?CMA、CNAS软件安全测试报告又该如何获取呢? 软件安全性测试是一种基于黑盒测试的方…

数据可视化-ECharts Html项目实战(3)

在之前的文章中&#xff0c;我们学习了如何创建堆积折线图&#xff0c;饼图以及较难的瀑布图并更改图标标题。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 …

C#类型转换

C#类型转换 隐式类型转换 类型转换从根本上说是类型铸造&#xff0c;或者说是把数据从一种类型转换为另一种类型。在 C# 中&#xff0c;类型铸造有两种形式&#xff1a; 隐式类型转换 - 这些转换是 C# 默认的以安全方式进行的转换, 不会导致数据丢失。例如&#xff0c;从小的…

深入理解与实践AB测试:从理论到实战案例解析

一、引言 在互联网产品优化和运营策略制定中&#xff0c;AB测试&#xff08;也称为分组测试或随机化对照实验&#xff09;是一种科学且严谨的方法。它通过将用户群体随机分配至不同的实验组&#xff08;通常是A组和B组&#xff09;&#xff0c;对比不同版本的产品或策略对关键…

CMake+vcpkg+VS2022配置github上的cmake开源项目外部库

我们以采用 GitHub 上的开源库 cuda-bundle-adjustment 为例&#xff0c;其不能直接用vcpkg进行安装&#xff0c;只能通过cmake编译后链接到VS2022。 将 cuda-bundle-adjustment 库通过 CMake 编译链接到 Visual Studio 2022 步骤操作&#xff1a; 克隆存储库&#xff1a;使用…

【实验01 扩展实验】C#桌面项目:简易计算器

【实验要求】 &#xff08;1&#xff09;新建一个C#桌面项目Calc&#xff0c;实现简易计算器功能&#xff0c;界面如图1所示。 &#xff08;2&#xff09;计算方式&#xff1a;通过点击对应的按钮&#xff0c;输入第1个数&#xff08;可以是整数或实数&#xff09;&#xff0c…

呵护地球不止一小时 食品行业如何为地球减负

“关爱地球,熄灯一小时”。作为全世界规模最大的应对气候变化行动之一,“地球一小时”自2007年进入中国以来,一直广受关注,社会各界竞相支持并参与。2024年,该活动以“为地球献出一小时”为主题,呼吁社会各界以实际行动为地球减负。 作为与大众日常生活息息相关的食品企业该如何…