之前我们在配置流控规则时,可以根据origin参数来对调用方进行限流。

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用Sentinel的黑白名单控制的功能,这就是授权规则

黑白名单也是根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

调用方信息通过ContextUtil.enter(resourceName, origin)方法中的origin参数传入。

可以实现接口RequestOriginParser来获取请求中的某个参数来标明调用方身份,例如可以从请求的Header中获取source字段来标明调用方身份,然后可以将source字段配置在授权规则中,可以根据source的值进行限制。

授权规则的配置

黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即授权规则的作用对象
  • limitApp:对应的黑名单/白名单,不同origin用逗号进行分隔,如appA,appB
  • strategy:限制模式,AUTHORITY_WHITE为白名单模式,AUTHORITY_BLACK为黑名单模式,默认为白名单模式

白名单:位于白名单内的资源可通过,不在白名单范围内的不可通过。

黑名单:位于黑名单内的资源不可通过,不在黑名单范围内的资源可通过。

【sentinel】授权规则详解及源码分析_springboot

授权规则的使用

比如我们希望控制对资源authorityTest的访问设置白名单,只有来源为appA和appB的请求才可通过,则可以配置如下白名单规则:

package com.morris.user.demo;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.Objects;

/**
 * 授权规则的使用
 */
@Slf4j
public class AuthorityDemo {
    public static void main(String[] args) {
        String origin = "appC";
        String resourceName = "authorityTest";
        // 手动创建Context,设置Origin
        ContextUtil.enter(resourceName, origin);
        
        // 配置授权规则,白名单模式
        AuthorityRule ruleB = new AuthorityRule()
                .setResource(resourceName)
                .setLimitApp("appA,appB")
                .as(AuthorityRule.class)
                .setStrategy(RuleConstant.AUTHORITY_WHITE);

        AuthorityRuleManager.loadRules(Collections.singletonList(ruleB));

        Entry entry = null;
        try {
            // Sentinel源码入口
            entry = SphU.entry(resourceName);
            log.info("业务逻辑... ");
        } catch (BlockException e) {
            log.error("block exception ", e);
        } finally {
            if (Objects.nonNull(entry)) {
                entry.exit();
            }
            System.exit(0);
        }

    }
}

授权规则的源码分析

授权规则的校验逻辑主要是通过AuthoritySlot实现的。

AuthoritySlot#entry会在调用目标方法之前进行校验是否在黑白名单内。

com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#entry

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);
}

    void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
    // 获取通过AuthorityRuleManager.loadRules()加载的授权规则
    Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

    if (authorityRules == null) {
        return;
    }

    // 根据资源名查找授权规则
    Set<AuthorityRule> rules = authorityRules.get(resource.getName());
    if (rules == null) {
        return;
    }

    for (AuthorityRule rule : rules) {
        // 遍历所有的授权规则,逐条校验
        if (!AuthorityRuleChecker.passCheck(rule, context)) {
            // 如果有一条规则不通过,则抛出AuthorityException,AuthorityException是BlockException的子类
            throw new AuthorityException(context.getOrigin(), rule);
        }
    }
}

拿到请求中的origin来源与规则中配置的app一一比较,判断是否在黑白名单内。

com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleChecker#passCheck

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

    // Empty origin or empty limitApp will pass.
    if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
        return true;
    }

    // Do exact match with origin name.
    // 先粗略匹配,再精确匹配
    int pos = rule.getLimitApp().indexOf(requester);
    boolean contain = pos > -1;

    if (contain) {
        boolean exactlyMatch = false;
        // 按逗号进行分割
        String[] appArray = rule.getLimitApp().split(",");
        for (String app : appArray) {
            // 没有处理空格
            // 精确匹配
            if (requester.equals(app)) {
                exactlyMatch = true;
                break;
            }
        }

        contain = exactlyMatch;
    }

    int strategy = rule.getStrategy();
    if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
        // 在黑名单内
        return false;
    }

    if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
        // 不在白名单内
        return false;
    }

    return true;
}