sentinel黑白名单权限控制

news2024/11/18 15:30:12

黑白名单权限控制

规则配置

规则创建

  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 流量治理框架 - 编程界的小學生 )

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

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

相关文章

2024年普通人的创业机会在哪里?2024热门创业项目!2024普通人想翻身的风口行业!

创业千万别冲动&#xff0c;社区团购代理创业失败案例&#xff01; 是不是一开始挺看好这个赛道&#xff0c;看别人做的风生水起&#xff0c;以为不难&#xff0c;真正开始做才发现不好做&#xff0c;没有先天优势&#xff0c;货源和客源从零开始积累&#xff0c;开始就是摸着石…

Qt学习--QT Creator使用基本介绍

话不多说&#xff0c;直接开搞&#xff0c;笔者用的是5.12.9版本 双击打开QT Creator 显示这个界面 新建工程 然后出现这样的界面 点击运行 就弹出了一个这个&#xff0c;空的&#xff0c;因为我们啥也没写

JUC并发编程(四)

1、同步模式保护性暂停 用一个线程等待另一个线程的执行结果 有一个结果需要从一个线程传递到另一个线程&#xff0c;让他们关联同一个中间类。如果有结果不断从一个线程到另一个线程那么可以使用消息队列&#xff08;见生产者/消费者&#xff09;。JDK 中&#xff0c;join 的…

人物百度百科如何创建?人物类词条编辑指南

创建人物百度百科是一项既具有挑战性的工作。下面&#xff0c;伯乐网络传媒就来给大家详细介绍如何创建人物百度百科&#xff0c;包括准备工作、创建步骤以及常见问题解答。 一、创建人物百度百科的准备工作 1. 人物百科词条创建要求 百度百科对创建人物词条有一定的要求&…

学嵌入式真的很烧钱吗?

如果是走嵌入式单片机方向&#xff0c;这篇内容&#xff0c;很适合预算1000以下的&#xff0c;作为发育参考。 下面是我2011年的入行成本&#xff1a; 买了智能小车&#xff0c;还有51开发板&#xff0c;杂七杂八&#xff0c;可能一共不到1000。 一开始迷之自信了&#xff0c;买…

HTML静态网页成品作业(HTML+CSS)——24节气之冬至介绍(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

误删.idea后的svn菜单找回

最近做开发maven出了问题总是找不到已有的包&#xff0c;最后相信大力出奇迹删除.idea目录重启idea&#xff0c;结果问题没解决&#xff0c;给我svn搞没了。无奈重新研究恢复svn&#xff0c;这里记录一下我成功的方法。 我是之前做过配置&#xff0c;删除.idea后消失的svn相关…

2024湖南消费促进年启动仪式在益阳举行

优化消费环境,激发消费活力。值3月15日消费者权益日,由湖南省商务厅、益阳市人民政府主办,益阳市商务局承办的2024湖南消费促进年(春季)启动仪式暨益阳消费促进系列活动在益阳佳宁娜广场隆重举行。湖南省各市州商务部门领导、各区县市商务主管部门领导、参展协会代表以及100多家…

《算法设计与分析第二版》100行 C语言实现 广度度优先算法 BFS——最短距离

抄录自课本P157页。 #include <stdio.h> #define MAXQ 100 // 队列大小 #define MAxN 10 // 最大迷宫大小 int n8; // 迷宫大小 char Maze [MAxN][MAxN] {{O,X,X,X,X,X,X,X,},{O,O,O,X,O,X,O,X,},{X,X,O,O,O,X,O,X,},{X,X,O,X,O,X,X,X,},…

普通人搞副业,空闲时间做,月入5w+

我是电商珠珠 大家会发现&#xff0c;朱砂越来越火&#xff0c;不仅是因为它好看&#xff0c;而且商家对外扬言可以招财。现在的人对爱情不屑一顾&#xff0c;财神殿里可以长跪不起&#xff0c;人人都想求财&#xff0c;想要在空余时间搞副业赚大钱&#xff0c;但做什么还没有…

掌握 Swagger annotations(注解):完全指南与最佳实践

利用 Swagger 注解增强 API 理解 Swagger 提供的注解集是其框架中定义 API 规范和文档的重要工具。这些注解在代码里标注重要部分&#xff0c;为 Swagger 的解析工作铺路&#xff0c;进而生成详尽的 API 文档。开发者编写的注释能够被转换成直观的文档&#xff0c;并展现API端…

【呼市经开区建设服务项目水、电能耗监测 数采案例】

一、项目背景及需求 项目地点位于内蒙古呼和浩特市&#xff0c;呼市数字经开区建设服务项目。属于企业用能数据采集、能耗监测板块子项目。 针对水、电能耗数据采集&#xff0c;结合现场客观因素制约&#xff0c;数据采集方面存在较大难度。大多数国网电表485接口由于封签限制&…

B009-springcloud alibaba 服务配置 Nacos Config

目录 服务配置中心介绍Nacos Config入门Nacos Config深入配置动态刷新方式一: 硬编码方式方式二: 注解方式(推荐) 配置共享同一个微服务的不同环境之间共享配置不同微服务中间共享配置 nacos的几个概念 服务配置中心介绍 首先我们来看一下,微服务架构下关于配置文件的一些问题…

031—pandas 读取解析实验室数据至DataFrame

前言 某个科研实验室在进行一项物理实现&#xff0c;实验仪器会输出一个 txt 文本的数据&#xff0c;研究人员需要从这个文本中将数据结构化才能进行进行统计分析。 在为个解析和分析过程中&#xff0c;他们选择了 Python 的 pandas 库来完成这些操作。我们今天来完成这这个 t…

一口气看完明朝276年历史

明朝是中国历史上最后一个由汉人建立的大一统封建王朝&#xff0c;建立于公元1368年&#xff0c;亡于公元1644年&#xff0c;国祚276年&#xff0c;传12世16帝。 太祖建国 太祖&#xff08;1368~1398&#xff09; 公元1368年&#xff0c;朱元璋在南京应天府建元称帝&#xff…

Linux 中搭建 主从dns域名解析服务器

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; Linux专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; ————前言———— 主从&#xff08;Master-Slave&#xff09;DNS架构是一种用于提高DNS系统可靠性和性能的配置方式。…

兼顾稳定和性价比的跨国企业SD-WAN组网

随着全球业务不断扩张&#xff0c;跨国企业面临着跨域网络的复杂性和不稳定性带来的挑战。不同地区分支机构的数据互通和协作常常受到制约&#xff0c;而在网络问题出现后&#xff0c;排查多方问题导致高昂的部署和运维成本。尽管直连方案在表面上看似省钱&#xff0c;但由于不…

爱恩斯坦棋小游戏使用C语言+ege/easyx实现

目录 1、游戏介绍和规则 2、需要用到的头文件 3、这里我也配上一个ege和easyx的下载链接吧&#xff0c;应该下一个就可以 4、运行结果部分展示 5、需要用到的图片要放在代码同一文件夹下 6、代码地址&#xff08;里面有需要用到的图片&#xff09; 1、游戏介绍和规则 规则如…

few shot vid2vid(Few-shot Video-to-Video Synthesis)论文理解

代码&#xff1a;https://github.com/NVlabs/few-shot-vid2vid 论文&#xff1a;https://arxiv.org/abs/1910.12713

centos虚拟机设置静态IP地址

vim编辑文件 /etc/sysconfig/network-scripts/ifcfg-ens33 重启即可。