之前我们在配置流控规则时,可以根据origin参数来对调用方进行限流。
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用Sentinel的黑白名单控制的功能,这就是授权规则。
黑白名单也是根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
调用方信息通过ContextUtil.enter(resourceName, origin)
方法中的origin参数传入。
可以实现接口RequestOriginParser来获取请求中的某个参数来标明调用方身份,例如可以从请求的Header中获取source字段来标明调用方身份,然后可以将source字段配置在授权规则中,可以根据source的值进行限制。
授权规则的配置
黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:
- resource:资源名,即授权规则的作用对象
- limitApp:对应的黑名单/白名单,不同origin用逗号进行分隔,如
appA,appB
- strategy:限制模式,AUTHORITY_WHITE为白名单模式,AUTHORITY_BLACK为黑名单模式,默认为白名单模式
白名单:位于白名单内的资源可通过,不在白名单范围内的不可通过。
黑名单:位于黑名单内的资源不可通过,不在黑名单范围内的资源可通过。
授权规则的使用
比如我们希望控制对资源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;
}