API 策略

API 具体策略可通过流量入口进行配置,分为全局策略配置和具体 API 策略配置。

  • 全局策略配置

    全局策略配置对所有 API 生效。

    API 策略 - 图1

  • 具体 API 策略配置

    对指定 API 进行配置,策略仅对该 API 生效。

    API 策略 - 图2

配置 API 策略时,可以选择某个策略继承全局策略的配置,也可以选择使用自己的独立配置。

API 策略 - 图3

tip 提示 开启 使用全局策略 后,页面将展示全局配置的内容,配置需确认并提交后方可生效。

流量接收转发

API 策略 - 图4

  • 强制跳转 HTTPS

    用于控制特定域名或特定 API,强制跳转或不跳转 HTTPS。

    tip 提示

    若已在 SLB 设置了 HTTPS 强制跳转,则此处配置不会生效。

  • 入口域名透传

    关闭策略或使用默认配置时,后端服务接收到的请求 Host 头是创建 API 时填写的转发地址,客户端访问的入口域名可以通过 X-Forwarded-Host 请求头获取。

    若后端服务希望直接从 Host 头中获取入口域名,请开启 入口域名透传

  • 请求/应答缓冲区

    开启策略或使用默认配置时,网关会分别为客户端请求和后端应答设立缓冲区。

    请求缓冲区的作用是接收到客户端完整的 HTTP 请求后,再转发给后端服务,从而在客户端网络上行速度慢时,避免和后端服务建立长时间的连接,减少连接数带来的压力。应答缓冲区则是将后端返回的应答一次性接收,当客户端网络下行速度慢时,同样可以避免和后端服务建立长时间的连接。

    若希望后端能立即接收到客户端的请求首字节,请关闭请求缓冲区。若希望客户端能立即接收到服务端的应答首字节(TTFB 优化),请关闭应答缓冲区。

  • 客户端请求限制

    当 HTTP 请求的 Body 大小超过限制时,会返回 HTTP 413 Request Entity Too Large 的错误。

  • 超时时间设置

    • 客户端请求超时:网关接收客户端请求的超时。
    • 客户端应答超时:网关向客户端发送应答的超时。
    • 后端建连超时:网关与后端服务建立连接的超时。
    • 后端请求超时:网关向后端服务发送请求的超时。
    • 后端应答超时:网关接收后端服务应答的超时。

跨域访问

API 策略 - 图5

配置项 含义
允许的 HTTP 方法 作为跨域应答头 Access-Control-Allow-Methods 的值
允许的 HTTP 请求头 作为跨域应答头 Access-Control-Allow-Headers 的值(因浏览器兼容性问题,请勿配置为通配符 “*”)
允许的跨域地址 作为跨域应答头 Access-Control-Allow-Origin 的值
允许携带 Cookie 是否设置跨域应答头 Access-Control-Allow-Credentials 为 true
预检请求缓存时间 作为跨域应答头 Access-Control-Max-Age 的值

tip 提示

  • 允许的跨域地址提供了默认值:$from_request_origin_or_referer。该变量代表优先从请求头 Origin 中获取跨域地址,若失败则从请求头 Referer 中获取,若仍失败则该值为 “*”。

  • 允许的 HTTP 请求头提供了默认值:$http_access_control_request_headers。该变量代表取请求头 Access-Control-Request-Headers 的值作为应答头的值。

  • 以上提供的默认值便于解决跨域的业务问题,但安全性较低。对于生产环境,建议为 Access-Control-Allow-Origin 配置准确的域名,以避免跨域风险。

自定义 Nginx 配置

API 策略 - 图6

平台支持 Nginx 所有 Location 区块下的指令配置,具体请参见 Nginx 官方文档

示例一:增加客户端的 HTTP 应答头

  1. add_header hello world;

添加该配置后,客户端收到的 HTTP 应答头中会增加 hello:world

示例二:增加给后端服务的 HTTP 请求头

  1. proxy_set_header hello world;

添加该配置后,后端服务收到的 HTTP 请求头中会增加 hello:world

tip 提示

  • 对于自定义设置应答头,建议使用 more_set_headers 指令代替 add_header,实现应答头的覆盖,而非追加。

  • 配置方式:more_set_headers "hello: world";,具体请参见 more_set_headers

IP 拦截

API 策略 - 图7

  • 用户 IP 来源

    若客户端直连网关,或者 SLB 配置了 TCP 协议转发,使用对端 IP 地址即可识别用户 IP;若 SLB 配置了 HTTP/HTTPS 协议转发,请选择从请求头 x-forwarded-for 中获取用户 IP。

  • IP 黑白名单

    • 黑名单模式:访问列表中 IP 时返回 403 状态码。
    • 白名单模式:访问列表外 IP 时返回 403 状态码。
  • CC 防护

    针对用户 IP,计算每个 IP 的并发连接数和请求速率,超过则返回 503 状态码。

    此处的请求速率针对两个连续请求的间隔来计算,例如配置了 10 请求/分钟,若两个请求间隔小于 6 秒则返回 503。

服务负载保护

API 策略 - 图8

  • 最大吞吐

    网关以毫秒为粒度衡量服务吞吐。例如最大吞吐为 10 次/秒,即表示每间隔 100 毫秒,仅能通过 1 个请求。若当前请求到达网关的时间距上一请求小于 100 毫秒,则网关将认为当前请求速率已超过服务的吞吐。

  • 最大额外延时

    配置最大额外延时,可在网关判定请求速率超过吞吐时,不会立即拒绝请求,而是将请求放入一个排队队列,延后转发给后端服务,从而实现对流量的削峰填谷。

    请求实际延后转发的时间,取决于当前排队队列长度,配置的最大额外延时即决定了队列的最大长度。

    例如最大吞吐为 10 次/秒,最大额外延时为 500 毫秒,则实际队列长度为 10 * 0.5 = 5(次)。若在 100 毫秒内接连收到 7 个请求,因请求之间时间间隔均小于 100 毫秒,超过限定的吞吐速率,因此第 2~6 个请求均将进入排队队列,并依次添加 100 毫秒、200 毫秒、300 毫秒、400 毫秒、500 毫秒的延时,此时队列已满,第 7 个请求将被直接拒绝访问。

    warning 警告

    1. 网关以毫秒为粒度计算吞吐、延时,因此若请求间隔小于 1 毫秒,即配置吞吐大于 1000 次/秒时,削峰填谷的计算会出现一定误差(给未超过最大吞吐的请求附加额外延时),可通过扩容网关节点降低误差,例如 10 个网关节点即可在 10000 次/秒的场景下确保削峰填谷的准确性。
    2. 最大额外延时决定队列的最大长度,队列最大长度决定支持的瞬时并发值大小。上述示例中队列长度为 5,导致 100 毫秒内收到的第 7 个请求被拒绝访问。若 1 秒内的请求均在 100 毫秒内瞬发到来,则实际上 1 秒内仅能通过 6 个请求,与配置的 10 次/秒的预期不一致。配置最大额外延时为 1 秒,可确保网关的限流机制在任意场景下均可达到预期。
    3. 在 Erda 1.3 版本之后,若用户将最大额外延时配置为 0,则网关不会为超过最大吞吐速率的请求附加额外延时,并且将瞬时并发上限设置为与最大吞吐一致(保障用户预期)。此类设置可在对延时相对敏感的业务场景下使用。请注意此时的限流已无法对流量进行削峰填谷,瞬时并发也有将后端服务击穿的风险。
  • 自定义状态码和应答

    当网关接收到的请求速率超过吞吐速率时,网关将根据配置返回客户端拒绝状态码和拒绝应答。

    拒绝状态码非 3xx 时,拒绝应答将作为 HTTP Body 返回;拒绝状态码为 3xx 时,拒绝应答需配置一个 HTTP 地址,用于重定向被拒绝的请求。

    例如,设计一个对用户友好的”稍后重试”静态页面放在 CDN 上,同时配置拒绝状态码为 302,拒绝应答为该静态页面的 CDN 链接。

跨站防护(CSRF 校验)

API 策略 - 图9

用户在访问恶意站点时会触发恶意脚本。跨站攻击能够在用户无感知的情况下,发起修改用户信息的请求。由于浏览器记录了用户 Cookie,该请求便可以顺利通过认证,进行恶意篡改。具体示意如下:

API 策略 - 图10

跨站防护开启后,会在用户登录成功时种下 CSRF Token,同时配合前端改造,对所有请求均带上 CSRF Token。网关收到请求后会对 CSRF Token 进行校验,确认属于当前用户后,才会将请求正常转发给后端。具体示意如下:

API 策略 - 图11

配置项 含义
鉴别用户的 Cookie 名称 用于生成 CSRF Token,未携带该 Cookie 则不会进行 CSRF 校验,后端识别该 Cookie 过期时需配合前端进行清除,避免缺失 CSRF Token 导致登录报错
关闭校验的 HTTP 方法 对于这类请求方法,将跳过 CSRF 校验,但仍会种下 CSRF Token 的 Cookie
Token 名称 网关将生成的 CSRF Token 设置在该名称的 Cookie 里,前端需从 Cookie 中获取 CSRF Token,带在同名请求头里发起请求
Token Cookie 的生效域名 如未填写,则仅针对发起请求的域名种下 CSRF Token 的 Cookie
Cookie 开启 Secure 属性 开启 Secure 后,所有 HTTP 请求均无法通过 CSRF 校验
Token 过期时间 过期 Token 将无法通过校验
Token 更新周期 请求携带 Token 签发时间超过更新周期时,将重新签发
校验失败的状态码 CSRF 校验失败时返回的 HTTP 状态码
校验失败的应答 CSRF 校验失败时返回的 HTTP 应答

基于第三方服务的访问控制 SBAC (Server Based Access Control)

基于第三方服务的访问控制 SBAC (Server Based Access Control) 是一种接口访问控制方式,启用该策略后,请求经过 API 网关时,API 网关会先携带参数请求用户提供的 API,根据该 API 返回结果决定是否准许该请求到达后端服务。

API 策略 - 图12

配置项 含义
Access Control API 用于访问控制的 API. 网关携带特定参数请求该 API,若返回非 2xx 响应,则拦截请求
HTTP 方法 要进行访问控制的方法,不填则对所有方法进行访问控制
匹配规则 正则表达式的列表,要请求的 path 若能匹配到任意一个正则表达式,就要进行访问控制。不填则默认对该 path 进行访问控制
携带请求头 网关请求访问控制服务 API 的请求体中要携带的原始请求头名称列表. ‘*’ 表示携带所有请求头(最多 1000 个)
携带 Cookie 网关请求访问控制服务 API 的请求体中携带的请求头列表中包含 Cookie
携带 Raw Body 网关请求访问控制服务 API 的请求体中携带原始的请求体

注意,Access Control 服务和 API 需要开发者自行实现,开发者应当评估开启策略后请求这个 API 造成的开销。 Access Control API 接口协议:

  1. openapi: 3.0.3
  2. info:
  3. title: ServerBasedAccessControl
  4. description: |-
  5. ServerBasedAccessControl
  6. 基于服务的访问控制
  7. version: 1.0.0
  8. paths:
  9. "/{access-control-api}":
  10. parameters:
  11. - name: access-control-api
  12. in: path
  13. required: true
  14. schema:
  15. type: string
  16. example: /access-contorl
  17. post:
  18. tags:
  19. - sbac
  20. summary: |-
  21. Access Contorl
  22. 访问控制
  23. requestBody:
  24. $ref: "#/components/requestBodies/AccessControlServer"
  25. responses:
  26. "200":
  27. description: |-
  28. successful operation if the response status code is 2xx
  29. 响应码为 2xx 时表示请求成功
  30. content:
  31. "application/json":
  32. schema:
  33. type: object
  34. components:
  35. requestBodies:
  36. AccessControlServer:
  37. content:
  38. "application/json":
  39. schema:
  40. type: object
  41. properties:
  42. "path":
  43. type: string
  44. description: |-
  45. raw query.
  46. 原始请求路径, 包含 query.
  47. example: /api?a=A&b=B
  48. "method":
  49. type: string
  50. description: |-
  51. http method in raw query. '*' means all headers(the max is 1000).
  52. 原始请求方法. '*' 表示所有请求头(最多 1000 个).
  53. example: GET
  54. "headers":
  55. type: object
  56. additionalProperties:
  57. items:
  58. type: array
  59. items:
  60. type: string
  61. description: |-
  62. the headers you want to check, it's value is a String array.
  63. 要传递的请求头, 值是 String 类型二维数组.
  64. example: {"Session-ID": ["xxx-yyy"], "UC-Token": ["aaa-bbb", "aaa-ccc"]}
  65. "body":
  66. type: object
  67. description: |-
  68. the raw body in the request.
  69. 要传递的原始请求体. 注意: 传递的是未经转义的原始请求体而非转义后的 String.
  70. example: {"key-1": value-1, "key-2": value-2}
  71. example: {"path": "/uniform/resource/identity?a=A&b=B", "method": "POST", "headers": {"Session-ID": ["xxx-yyy"], "UC-Token": ["aaa-bbb", "aaa-ccc"]}, "body": {"key-1": "value-1", "key-2": "value-2"}}
  72. required: true

接口请求体示例:

  1. {
  2. "path": "/uniform/resource/identity?a=A&b=B",
  3. "method": "POST",
  4. "headers": {
  5. "Session-ID": [
  6. "xxx-yyy"
  7. ],
  8. "UC-Token": [
  9. "aaa-bbb",
  10. "aaa-ccc"
  11. ]
  12. },
  13. "body": {
  14. "key-1": "value-1",
  15. "key-2": "value-2"
  16. }
  17. }