Talk about sentinel’s AuthoritySlot

  sentinel

Order

This article mainly studies sentinel’s AuthoritySlot

AuthoritySlot

com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java

public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
        throws Throwable {
        AuthorityRuleManager.checkAuthority(resourceWrapper, context, node, count);
        fireEntry(context, resourceWrapper, node, count, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }
}
  • Check authority to check

AuthorityRuleManager

com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java

public class AuthorityRuleManager {

    private static Map<String, List<AuthorityRule>> authorityRules
        = new ConcurrentHashMap<String, List<AuthorityRule>>();

    final static RulePropertyListener listener = new RulePropertyListener();

    private static SentinelProperty<List<AuthorityRule>> currentProperty
        = new DynamicSentinelProperty<List<AuthorityRule>>();

    static {
        currentProperty.addListener(listener);
    }

    public static void register2Property(SentinelProperty<List<AuthorityRule>> property) {
        synchronized (listener) {
            if (currentProperty != null) {
                currentProperty.removeListener(listener);
            }
            property.addListener(listener);
            currentProperty = property;
        }
    }

    /**
     * Load the authority rules to memory.
     *
     * @param rules list of authority rules
     */
    public static void loadRules(List<AuthorityRule> rules) {
        currentProperty.updateValue(rules);
    }

    public static void checkAuthority(ResourceWrapper resource, Context context, DefaultNode node, int count)
        throws BlockException {
        if (authorityRules == null) {
            return;
        }

        List<AuthorityRule> rules = authorityRules.get(resource.getName());
        if (rules == null) {
            return;
        }

        for (AuthorityRule rule : rules) {
            if (!rule.passCheck(context, node, count)) {
                throw new AuthorityException(context.getOrigin());
            }
        }
    }

    public static boolean hasConfig(String resource) {
        return authorityRules.containsKey(resource);
    }

    /**
     * Get a copy of the rules.
     *
     * @return a new copy of the rules.
     */
    public static List<AuthorityRule> getRules() {
        List<AuthorityRule> rules = new ArrayList<AuthorityRule>();
        if (authorityRules == null) {
            return rules;
        }
        for (Map.Entry<String, List<AuthorityRule>> entry : authorityRules.entrySet()) {
            rules.addAll(entry.getValue());
        }
        return rules;
    }

    private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {

        @Override
        public void configUpdate(List<AuthorityRule> conf) {
            Map<String, List<AuthorityRule>> rules = loadAuthorityConf(conf);

            authorityRules.clear();
            if (rules != null) {
                authorityRules.putAll(rules);
            }
            RecordLog.info("[AuthorityRuleManager] Authority rules received: " + authorityRules);
        }

        private Map<String, List<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
            if (list == null) {
                return null;
            }
            Map<String, List<AuthorityRule>> newRuleMap = new ConcurrentHashMap<String, List<AuthorityRule>>();
            for (AuthorityRule rule : list) {
                if (StringUtil.isBlank(rule.getLimitApp())) {
                    rule.setLimitApp(FlowRule.LIMIT_APP_DEFAULT);
                }

                String identity = rule.getResource();
                List<AuthorityRule> ruleM = newRuleMap.get(identity);
                // putIfAbsent
                if (ruleM == null) {
                    ruleM = new ArrayList<AuthorityRule>();
                    ruleM.add(rule);
                    newRuleMap.put(identity, ruleM);
                } else {
                    // One resource should only have at most one authority rule, so just ignore redundant rules.
                    RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: " + rule.toString());
                }
            }

            return newRuleMap;
        }

        @Override
        public void configLoad(List<AuthorityRule> value) {
            Map<String, List<AuthorityRule>> rules = loadAuthorityConf(value);

            authorityRules.clear();
            if (rules != null) {
                authorityRules.putAll(rules);
            }
            RecordLog.info("[AuthorityRuleManager] Load authority rules: " + authorityRules);
        }
    }

}
  • The checkAuthority method obtains the rules of the corresponding resources from the authorityRules, and then checks the rule.passCheck one by one.

AuthorityRule

com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java

public class AuthorityRule extends AbstractRule {

    /**
     * Mode: 0 for whitelist; 1 for blacklist.
     */
    private int strategy = RuleConstant.AUTHORITY_WHITE;

    public int getStrategy() {
        return strategy;
    }

    public void setStrategy(int strategy) {
        this.strategy = strategy;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (!(o instanceof AuthorityRule)) { return false; }
        if (!super.equals(o)) { return false; }

        AuthorityRule rule = (AuthorityRule)o;

        return strategy == rule.strategy;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + strategy;
        return result;
    }

    @Override
    public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
        String requester = context.getOrigin();

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

        // Do exact match with origin name.
        int pos = this.getLimitApp().indexOf(requester);
        boolean contain = pos > -1;

        if (contain) {
            boolean exactlyMatch = false;
            String[] appArray = this.getLimitApp().split(",");
            for (String app : appArray) {
                if (requester.equals(app)) {
                    exactlyMatch = true;
                    break;
                }
            }

            contain = exactlyMatch;
        }

        if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
            return false;
        }

        if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
            return false;
        }

        return true;
    }

    @Override
    public String toString() {
        return "AuthorityRule{" +
            "resource=" + getResource() +
            ", limitApp=" + getLimitApp() +
            ", strategy=" + strategy +
            "} ";
    }
}
  • The passCheck method matches limitApp through the context’s origin.

CommonFilter

com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java

public class CommonFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest sRequest = (HttpServletRequest)request;
        Entry entry = null;

        try {
            String target = FilterUtil.filterTarget(sRequest);
            target = WebCallbackManager.getUrlCleaner().clean(target);

            ContextUtil.enter(target);
            entry = SphU.entry(target, EntryType.IN);

            chain.doFilter(request, response);
        } catch (BlockException e) {
            HttpServletResponse sResponse = (HttpServletResponse)response;
            WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse);
        } catch (IOException e2) {
            Tracer.trace(e2);
            throw e2;
        } catch (ServletException e3) {
            Tracer.trace(e3);
            throw e3;
        } catch (RuntimeException e4) {
            Tracer.trace(e4);
            throw e4;
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }

    @Override
    public void destroy() {

    }
}
  • Currently, the servlet-based CommonFilter does not specify origin when calling ContextUtil.enter(target). currently, the default is “”

Summary

The AuthoritySlot is mainly used to match black and white lists. it is not perfect yet. origin was not specified when CommonFilter entered entry.

doc