sentinel黑白名单权限控制

2024-03-19 06:44

本文主要是介绍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);}// 静态内部类的方式实现 黑白名单Listenerprivate static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {// 规则初始化@Overridepublic synchronized void configLoad(List<AuthorityRule> value) {}// 规则变更@Overridepublic 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;@Overridepublic 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<>();// 省略上面代码...// 静态内部类的方式实现 黑白名单Listenerprivate static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {// 规则初始化@Overridepublic synchronized void configLoad(List<AuthorityRule> value) {authorityRules.updateRules(loadAuthorityConf(value));RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);}// 规则变更@Overridepublic 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> {// 省略其它代码...@Overridepublic boolean updateValue(T newValue) {if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);// 将传入的规则赋值给valuevalue = newValue;// 遍历通知所有监听者for (PropertyListener<T> listener : listeners) {// 这里调用了configUpdate, 即上边分析的configUpdate()// 具体全类名如下com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager.RulePropertyListener#configUpdatelistener.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> {@Overridepublic 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);}@Overridepublic 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进行校验, 如果不通过就抛出AuthorityExceptionif (!AuthorityRuleChecker.passCheck(rule, context)) {throw new AuthorityException(context.getOrigin(), rule);}}}
}

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

final class AuthorityRuleChecker {static boolean passCheck(AuthorityRule rule, Context context) {// 获取originString 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是否包含originboolean 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不在limitAppif (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 流量治理框架 - 编程界的小學生 )

这篇关于sentinel黑白名单权限控制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/825121

相关文章

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

SpringBoot AspectJ切面配合自定义注解实现权限校验的示例详解

《SpringBootAspectJ切面配合自定义注解实现权限校验的示例详解》本文章介绍了如何通过创建自定义的权限校验注解,配合AspectJ切面拦截注解实现权限校验,本文结合实例代码给大家介绍的非... 目录1. 创建权限校验注解2. 创建ASPectJ切面拦截注解校验权限3. 用法示例A. 参考文章本文

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

Linux权限管理与ACL访问控制详解

《Linux权限管理与ACL访问控制详解》Linux权限管理涵盖基本rwx权限(通过chmod设置)、特殊权限(SUID/SGID/StickyBit)及ACL精细授权,由umask决定默认权限,需合... 目录一、基本权限概述1. 基本权限与数字对应关系二、权限管理命令(chmod)1. 字符模式语法2.

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

nginx中端口无权限的问题解决

《nginx中端口无权限的问题解决》当Nginx日志报错bind()to80failed(13:Permissiondenied)时,这通常是由于权限不足导致Nginx无法绑定到80端口,下面就来... 目录一、问题原因分析二、解决方案1. 以 root 权限运行 Nginx(不推荐)2. 为 Nginx

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

Spring如何使用注解@DependsOn控制Bean加载顺序

《Spring如何使用注解@DependsOn控制Bean加载顺序》:本文主要介绍Spring如何使用注解@DependsOn控制Bean加载顺序,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录1.javascript 前言2. 代码实现总结1. 前言默认情况下,Spring加载Bean的顺