来源访问控制(黑白名单)
概念
Sentinel 提供了黑白名单限制资源能否通过的功能。如果配置了白名单,则只有位于白名单的请求来源的对应的请求才能通过;如果配置了黑名单,则位于黑名单的请求来源对应的请求不能通过。
实际操作
在 Nacos 控制台的配置管理/配置列表中,在 public 的命名空间中创建一个如下配置:
dataId:spring-cloud-demo-provider-sentinel-authority
group:DEFAULT_GROUP
配置内容如下:
[
{
"resource": "/hello",
"limitApp": "white-origin",
"strategy": 0
},
{
"resource": "/hello/say",
"limitApp": "black-origin",
"strategy": 1
}
]
strategy 为 0 表示白名单;为 1 表示黑名单。
limitApp 如果有多个请求来源,则使用逗号分隔。
对应的客户端的配置文件如下:
spring:
application:
name: spring-cloud-demo-provider
cloud:
nacos:
discovery:
server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848
enabled: true
sentinel:
transport:
dashboard: 127.0.0.1:9000
eager: true
web-context-unify: false
datasource:
authority-nacos-datasource:
nacos:
server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848
group-id: DEFAULT_GROUP
namespace: public
data-id: ${spring.application.name}-sentinel-authority
data-type: json
rule-type: authority
username: nacos
password: nacos
然后注册一个 RequestOriginParser 类型的 Bean。
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getHeader("origin");
if (StringUtils.isBlank(origin)) {
origin = "default";
}
return origin;
}
}
请求时,在请求头添加一对 origin, whiteOrigin 或者 origin, blackOrigin 就能看到黑白名单对于特定资源的限制效果了。
实际操作中,个人发现如果被黑白名单限制的话,程序不会抛出异常,而是响应中输出 Blocked by Sentinel (flow limiting)。
AuthoritySlot
负责对请求来源的授权规则进行校验。
@Spi(order = Constants.ORDER_AUTHORITY_SLOT)
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);
// 交给下一个ProcessorSlot继续处理
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
// 交给下一个ProcessorSlot继续处理
fireExit(context, resourceWrapper, count, args);
}
void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
// 加载所有资源的授权规则
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)) {
throw new AuthorityException(context.getOrigin(), rule);
}
}
}
}
接下来看下 AuthorityRuleChecker 的 passCheck 方法的内部逻辑。
static boolean passCheck(AuthorityRule rule, Context context) {
// 获取上下文记录的请求来源
String requester = context.getOrigin();
// 如果请求来源为空或者授权规则的limitApp为空,则返回true,表示校验通过
if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
return true;
}
// 判断授权规则的limitApp中是否存在指定的请求来源
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();
// 如果设置了黑名单,并且授权规则中包含指定的请求来源,则返回false,表示校验不通过
if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
return false;
}
// 如果设置了白名单,并且授权规则中不包含指定的请求来源,则返回false,表示校验不通过
if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
return false;
}
// 其余请求,返回true,表示校验通过
return true;
}