默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。异常结果都是 flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。

基本规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

  • 白名单:来源(origin)在白名单内的调用者允许访问
  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

点击左侧菜单的授权,可以看到授权规则:

授权规则 - 图1

  • 资源名:就是受保护的资源,例如 /order/{orderId}
  • 流控应用:是来源者的名单,
    • 如果是勾选白名单,则名单中的来源被许可访问。
    • 如果是勾选黑名单,则名单中的来源被禁止访问。

我们允许请求从 gateway 到 order-service,不允许浏览器访问 order-service,那么白名单中就要填写网关的来源名称(origin)。

如何获取 origin

Sentinel 是通过 RequestOriginParser 这个接口的 parseOrigin 来获取请求的来源的。

  1. public interface RequestOriginParser {
  2. /**
  3. * 从请求request对象中获取origin,获取方式自定义
  4. */
  5. String parseOrigin(HttpServletRequest request);
  6. }

这个方法的作用就是从 request 对象中,获取请求者的 origin 值并返回。

默认情况下,sentinel 不管请求者从哪里来,返回值永远是 default,也就是说一切请求的来源都被认为是一样的值 default。

因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的 origin。

例如 order-service 服务中,我们定义一个 RequestOriginParser 的实现类:

  1. @Component
  2. public class HeaderOriginParser implements RequestOriginParser {
  3. @Override
  4. public String parseOrigin(HttpServletRequest request) {
  5. // 1.获取请求头
  6. String origin = request.getHeader("origin");
  7. // 2.非空判断
  8. if (StringUtils.isEmpty(origin)) {
  9. origin = "blank";
  10. }
  11. return origin;
  12. }
  13. }

我们会尝试从 request-header 中获取 origin 值。

给网关添加请求头

既然获取请求 origin 的方式是从 reques-header 中获取 origin 值,我们必须让所有从 gateway 路由到微服务的请求都带上origin头。

这个需要利用之前学习的一个 GatewayFilter 来实现,AddRequestHeaderGatewayFilter

修改 gateway 服务中的 application.yml,添加一个 defaultFilter:

  1. spring:
  2. cloud:
  3. gateway:
  4. default-filters:
  5. - AddRequestHeader=origin,gateway
  6. routes:
  7. # ...略

这样,从 gateway 路由的所有请求都会带上 origin 头,值为 gateway。而从其它地方到达微服务的请求则没有这个头。

配置授权规则

接下来,我们添加一个授权规则,放行 origin 值为 gateway 的请求。

授权规则 - 图2

自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。异常结果都是 flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。

异常类型

而如果要自定义异常时的返回结果,需要实现 BlockExceptionHandler 接口:

public interface BlockExceptionHandler {
    /**
     * 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
     */
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

这个方法有三个参数:

  • HttpServletRequest request:request 对象
  • HttpServletResponse response:response 对象
  • BlockException e:被 sentinel 拦截时抛出的异常

这里的 BlockException 包含多个不同的子类:

异常 说明
FlowException 限流异常
ParamFlowException 热点参数限流的异常
DegradeException 降级异常
AuthorityException 授权规则异常
SystemBlockException 系统规则异常

自定义异常处理

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;

        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }

        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

重启测试,在不同场景下,会返回不同的异常消息。