Add wasm plugin contribution introduction (#47)

This commit is contained in:
澄潭
2022-11-10 09:52:54 +08:00
committed by GitHub
parent ecba3a0265
commit 201c43105d
33 changed files with 1544 additions and 70 deletions

View File

@@ -0,0 +1,110 @@
# 功能说明
`basic-auth`插件实现了基于 HTTP Basic Auth 标准进行认证鉴权的功能
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | -------- | ------ | ---------------------------------------------------- |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ------------ | -------- | -------- | ------ | ------------------------ |
| `credential` | string | 必填 | - | 配置该consumer的访问凭证 |
| `name` | string | 必填 | - | 配置该consumer的名称 |
`_rules_` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `_match_route_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
| `_match_domain_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的域名 |
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求配置允许访问的consumer名称 |
**注意:**
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
# 配置示例
## 对特定路由或域名开启认证和鉴权
以下配置将对网关特定路由或域名开启 Basic Auth 认证和鉴权,注意凭证信息中的用户名和密码之间使用":"分隔,`credential`字段不能重复
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
consumers:
- credential: 'admin:123456'
name: consumer1
- credential: 'guest:abc'
name: consumer2
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
allow:
- consumer1
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
allow:
- consumer2
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name``consumer1`的调用者访问,其他调用者不允许访问;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name``consumer2`的调用者访问,其他调用者不允许访问。
### 根据该配置,下列请求可以允许访问:
**请求指定用户名密码**
```bash
# 假设以下请求将会匹配到route-a路由
# 使用 curl 的 -u 参数指定
curl -u admin:123456 http://xxx.hello.com/test
# 或者直接指定 Authorization 请求头,用户名密码使用 base64 编码
curl -H 'Authorization: Basic YWRtaW46MTIzNDU2' http://xxx.hello.com/test
```
认证鉴权通过后请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
### 下列请求将拒绝访问:
**请求未提供用户名密码返回401**
```bash
curl http://xxx.hello.com/test
```
**请求提供的用户名密码错误返回401**
```bash
curl -u admin:abc http://xxx.hello.com/test
```
**根据请求的用户名和密码匹配到的调用者无访问权限返回403**
```bash
# consumer2不在route-a的allow列表里
curl -u guest:abc http://xxx.hello.com/test
```
## 网关实例级别开启
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 Basic Auth 认证
```yaml
consumers:
- credential: 'admin:123456'
name: consumer1
- credential: 'guest:abc'
name: consumer2
```
# 相关错误码
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- | ------------------------------------------------------------------------------ | ---------------------- |
| 401 | Request denied by Basic Auth check. No Basic Authentication information found. | 请求未提供凭证 |
| 401 | Request denied by Basic Auth check. Invalid username and/or password | 请求凭证无效 |
| 403 | Request denied by Basic Auth check. Unauthorized consumer | 请求的调用方无访问权限 |

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,75 @@
# 功能说明
`bot-detect`插件可以用于识别并阻止互联网爬虫对站点资源的爬取
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| allow | array of string | 选填 | - | 配置匹配 User-Agent 请求头的正则表达式,匹配命中时将允许其访问 |
| deny | array of string | 选填 | - | 配置匹配 User-Agent 请求头的正则表达式,匹配命中时将屏蔽请求 |
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
`allow``deny` 字段可以均不配置,则执行默认的爬虫判断逻辑,通过配置 `allow` 字段可以将原本命中默认爬虫判断逻辑的请求放行,通过配置 `deny` 字段可以增加额外的爬虫判断逻辑。
默认的爬虫判断正则表达式集合如下:
```bash
# Bots General matcher 'name/0.0'
(?:\/[A-Za-z0-9\.]+|) {0,5}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\d+)(?:\.(\d+)(?:\.(\d+)|)|)
# Bots General matcher 'name 0.0'
(?:\/[A-Za-z0-9\.]+|) {0,5}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50})) (\d+)(?:\.(\d+)(?:\.(\d+)|)|)
# Bots containing spider|scrape|bot(but not CUBOT)|Crawl
((?:[A-z0-9]{1,50}|[A-z\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)
# Bots Pattern '/name-0.0'
/((?:Ant-)?Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)(?:\.(\d+))?)?
# Bots Pattern 'name/0.0'
\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)
# More bots
(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)
```
# 配置示例
## 放行原本命中爬虫规则的请求
```yaml
allow:
- ".*Go-http-client.*"
```
若不作该配置,默认的 Golang 网络库请求会被视做爬虫,被禁止访问
## 增加爬虫判断
```yaml
deny:
- "spd-tools.*"
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://example.com -H 'User-Agent: spd-tools/1.1'
curl http://exmaple.com -H 'User-Agent: spd-tools'
```
## 对特定路由或域名开启
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
allow:
- ".*Go-http-client.*"
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,80 @@
# 功能说明
`custom-response`插件支持配置自定义的响应,包括自定义 HTTP 应答状态码、HTTP 应答头,以及 HTTP 应答 Body。可以用于 Mock 响应,也可以用于判断特定状态码后给出自定义应答,例如在触发网关限流策略时实现自定义响应。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| status_code | number | 选填 | 200 | 自定义 HTTP 应答状态码 |
| headers | array of string | 选填 | - | 自定义 HTTP 应答头key 和 value 用`=`分隔 |
| body | string | 选填 | - | 自定义 HTTP 应答 Body |
| enable_on_status | array of number | 选填 | - | 匹配原始状态码,生成自定义响应,不填写时,不判断原始状态码 |
# 配置示例
## Mock 应答场景
```yaml
status_code: 200
headers:
- Content-Type=application/json
- Hello=World
body: "{\"hello\":\"world\"}"
```
根据该配置,请求将返回自定义应答如下:
```text
HTTP/1.1 200 OK
Content-Type: application/json
Hello: World
Content-Length: 17
{"hello":"world"}
```
## 触发限流时自定义响应
```yaml
enable_on_status:
- 429
status_code: 302
headers:
- Location=https://example.com
```
触发网关限流时一般会返回 `429` 状态码,这时请求将返回自定义应答如下:
```text
HTTP/1.1 302 Found
Location: https://example.com
```
从而实现基于浏览器 302 重定向机制,将限流后的用户引导到其他页面,比如可以是一个 CDN 上的静态页面。
如果希望触发限流时,正常返回其他应答,参考 Mock 应答场景配置相应的字段即可。
## 对特定路由或域名开启
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
body: "{\"hello\":\"world\"}"
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
enable_on_status:
- 429
status_code: 200
headers:
- Content-Type=application/json
body: "{\"errmsg\": \"rate limited\"}"
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,285 @@
# 功能说明
`hmac-auth`插件实现了基于 HMAC 算法为 HTTP 请求生成不可伪造的签名,并基于签名实现身份认证和鉴权
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ------------- | --------------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `date_offset` | number | 选填 | - | 配置允许的客户端最大时间偏移,单位为秒,根据请求头`Date`解析客户端 UTC 时间,可用于避免请求重放;未配置时,不做校验 |
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | ------ | ----------------------------------- |
| `key` | string | 必填 | - | 配置从请求的`x-ca-key`头中提取的key |
| `secret` | string | 必填 | - | 配置用于生成签名的secret |
| `name` | string | 必填 | - | 配置该consumer的名称 |
`_rules_` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `_match_route_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
| `_match_domain_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的域名 |
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求配置允许访问的consumer名称 |
**注意:**
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
# 配置示例
以下配置将对网关特定路由或域名开启 Hmac Auth 认证和鉴权,注意`key`字段不能重复
## 对特定路由或域名开启
```yaml
consumers:
- key: appKey-example-1
secret: appSecret-example-1
name: consumer-1
- key: appKey-example-2
secret: appSecret-example-2
name: consumer-2
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
allow:
- consumer-1
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
allow:
- consumer-2
```
每条匹配规则下的`allow`字段用于指定该匹配条件下允许访问的调用者列表;
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name``consumer-1`的调用者访问,其他调用者不允许访问;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name``consumer-2`的调用者访问,其他调用者不允许访问;
认证成功后请求的header中会被添加一个`X-Mse-Consumer`字段,其值为调用方的名称,例如`consumer-1`
## 网关实例级别开启
以下配置将对网关实例级别开启 Hamc Auth 认证
```yaml
consumers:
- key: appKey-example-1
secret: appSecret-example-1
name: consumer-1
- key: appKey-example-2
secret: appSecret-example-2
name: consumer-2
```
# 签名机制说明
## 配置准备
如上指引,在插件配置中配置生成和验证签名需要用的凭证配置
- key: 用于请求头 `x-ca-key` 中设置
- secret: 用于生成请求签名
## 客户端签名生成方式
### 流程简介
客户端生成签名一共分三步处理:
1. 从原始请求中提取关键数据,得到一个用来签名的字符串
2. 使用加密算法和配置的 `secret` 对关键数据签名串进行加密处理,得到签名
3. 将签名所相关的所有头加入到原始HTTP请求中得到最终HTTP请求
如下图所示:
![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/1745707061/p188113.png)
### 签名串提取流程
客户端需要从Http请求中提取出关键数据组合成一个签名串生成的签名串的格式如下
```text
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters
```
以上7个字段构成整个签名串字段之间使用\n间隔如果Headers为空则不需要加\n其他字段如果为空都需要保留\n。签名大小写敏感。下面介绍下每个字段的提取规则
- HTTPMethodHTTP的方法全部大写比如POST
- Accept请求中的Accept头的值可为空。建议显式设置 Accept Header。当 Accept 为空时,部分 Http 客户端会给 Accept 设置默认值为 `*/*`,导致签名校验失败。
- Content-MD5请求中的Content-MD5头的值可为空只有在请求存在Body且Body为非Form形式时才计算Content-MD5头下面是Java的Content-MD5值的参考计算方式
```java
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
```
- Content-Type请求中的Content-Type头的值可为空
- Date请求中的Date头的值当未开启`date_offset`配置时,可为空,否则将用于时间偏移校验
- Headers用户可以选取指定的header参与签名关于header的签名串拼接方式有以下规则
- 参与签名计算的Header的Key按照字典排序后使用如下方式拼接
```text
HeaderKey1 + ":" + HeaderValue1 + "\n"\+
HeaderKey2 + ":" + HeaderValue2 + "\n"\+
...
HeaderKeyN + ":" + HeaderValueN + "\n"
```
- 某个Header的Value为空则使用HeaderKey+":"+"\n"参与签名需要保留Key和英文冒号
- 所有参与签名的Header的Key的集合使用英文逗号分割放到Key为X-Ca-Signature-Headers的Header中
- 以下Header不参与Header签名计算X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date
- PathAndParameters: 这个字段包含PathQuery和Form中的所有参数具体组织形式如下
```text
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
```
注意:
1. Query和Form参数对的Key按照字典排序后使用上面的方式拼接
2. Query和Form参数为空时则直接使用Path不需要添加?
3. 参数的Value为空时只保留Key参与签名等号不需要再加入签名
4. Query和Form存在数组参数时key相同value不同的参数 取第一个Value参与签名计算
### 签名串提取示例
初始的HTTP请求
```text
POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
content-length:33
username=xiaoming&password=123456789
```
生成的正确签名串为:
```text
POST
application/json; charset=utf-8
application/x-www-form-urlencoded; charset=utf-8
Wed, 09 May 2018 13:30:29 GMT+00:00
x-ca-key:203753385
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-signature-method:HmacSHA256
x-ca-timestamp:1525872629832
/http2test/test?param1=test&password=123456789&username=xiaoming
```
### 签名计算流程
客户端从HTTP请求中提取出关键数据组装成签名串后需要对签名串进行加密及编码处理形成最终的签名
具体的加密形式如下,其中 `stringToSign` 是提取出来的签名串,`secret` 就是插件配置中填写的,`sign` 是最终生成的签名:
```java
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] secretBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA256"));
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
String sign = Base64.encodeBase64String(result);
```
总结一下,就是将 `stringToSign` 使用UTF-8解码后得到Byte数组然后使用加密算法对Byte数组进行加密然后使用Base64算法进行编码形成最终的签名。
### 添加签名流程
客户端需要将以下四个Header放在HTTP请求中传输给API网关进行签名校验
- x-ca-key取值APP Key必选
- x-ca-signature-method签名算法取值HmacSHA256或者HmacSHA1可选默认值为HmacSHA256
- x-ca-signature-headers所有签名头的Key的集合使用英文逗号分隔可选
- x-ca-signature签名必选
下面是携带签名的整个HTTP请求的示例
```text
POST /http2test/test?param1=test HTTP/1.1
host:api.aliyun.com
accept:application/json; charset=utf-8
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-key:203753385
x-ca-signature-method:HmacSHA256
x-ca-signature-headers:x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method
x-ca-signature:xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM=
content-length:33
username=xiaoming&password=123456789
```
## 服务端签名验证方式
### 流程简介
服务器验证客户端签名一共分四步处理:
1. 从接收到的请求中提取关键数据,得到一个用来签名的字符串
2. 从接收到的请求中读取 `key` ,通过 `key` 查询到对应的 `secret`
3. 使用加密算法和 `secret` 对关键数据签名串进行加密处理,得到签名
4. 从接收到的请求中读取客户端签名,对比服务器端签名和客户端签名的一致性
如下图所示:
![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/1745707061/p188116.png)
## 签名排错方法
网关签名校验失败时会将服务端的签名串StringToSign放到HTTP Response的Header中返回到客户端Key为X-Ca-Error-Message用户只需要将本地计算的签名串StringToSign与服务端返回的签名串进行对比即可找到问题
如果服务端与客户端的StringToSign一致请检查用于签名计算的APP Secret是否正确
因为HTTP Header中无法表示换行因此StringToSign中的换行符都被替换成`#`,如下所示:
```text
X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`
```
# 相关错误码
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
| 401 | Invalid Key | 请求头未提供 x-ca-key或者 x-ca-key 无效 |
| 401 | Empty Signature | 请求头未提供 x-ca-signature 签名串 |
| 400 | Invalid Signature | 请求头 x-ca-signature 签名串,与服务端计算得到签名不一致 |
| 400 | Invalid Content-MD5 | 请求头 content-md5 不正确 |
| 400 | Invalid Date | 根据请求头 date 计算时间偏移超过配置的 date_offset |
| 413 | Request Body Too Large | 请求 Body 超过限制大小32 MB |
| 413 | Payload Too Large | 请求 Body 超过全局配置 DownstreamConnectionBufferLimits |
| 403 | Unauthorized Consumer | 请求的调用方无访问权限 |

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,400 @@
# 功能说明
`jwt-auth`插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能支持从HTTP请求的URL参数、请求头、Cookie字段解析JWT同时验证该Token是否有权限访问。
本插件和`安全能力->认证鉴权`中JWT认证的区别是额外提供了调用方身份识别的能力支持对不同调用方配置不同的JWT凭证。
# 详细说明
## 1、基于token的认证
### 1.1 简介
很多对外开放的API需要识别请求者的身份并据此判断所请求的资源是否可以返回给请求者。token就是一种用于身份验证的机制基于这种机制应用不需要在服务端保留用户的认证信息或者会话信息可实现无状态、分布式的Web应用授权为应用的扩展提供了便利。
### 1.2 流程描述
![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/2336348951/p135822.png)
上图是网关利用JWT实现认证的整个业务流程时序图下面我们用文字来详细描述图中标注的步骤
1. 客户端向API网关发起认证请求请求中一般会携带终端用户的用户名和密码
2. 网关将请求直接转发给后端服务;
3. 后端服务读取请求中的验证信息比如用户名、密码进行验证验证通过后使用私钥生成标准的token返回给网关
4. 网关将携带token的应答返回给客户端客户端需要将这个token缓存到本地
5. 客户端向API网关发送业务请求请求中携带token
6. 网关使用用户设定的公钥对请求中的token进行验证验证通过后将请求透传给后端服务
7. 后端服务进行业务处理后应答;
8. 网关将业务应答返回给客户端。
在这个整个过程中, 网关利用token认证机制实现了用户使用自己的用户体系对自己API进行授权的能力。下面我们就要介绍网关实现token认证所使用的结构化令牌Json Web Token(JWT)。
### 1.3 JWT
#### 1.3.1 简介
Json Web TokeJWT是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准RFC7519。JWT一般可以用作独立的身份验证令牌可以包含用户标识、用户角色和权限等信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息特别适用于分布式站点的登录场景。
#### 1.3.2 JWT的构成
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`
如上面的例子所示JWT就是一个字符串由三部分构成
- Header头部
- Payload数据
- Signature签名
**Header**
JWT的头部承载两个信息
- 声明类型这里是JWT
- 声明加密的算法
网关支持的加密算法如下:
```text
ES256, ES384, ES512,
HS256, HS384, HS512,
RS256, RS384, RS512,
PS256, PS384, PS512,
EdDSA
```
完整的头部就像下面这样的JSON
```js
{
'typ': 'JWT',
'alg': 'HS256'
}
```
然后将头部进行Base64编码该编码是可以对称解码的构成了第一部分。
`eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9`
**Payload**
载荷就是存放有效信息的地方。定义细节如下:
```text
iss令牌颁发者。表示该令牌由谁创建该声明是一个字符串
sub: Subject Identifieriss提供的终端用户的标识在iss范围内唯一最长为255个ASCII个字符区分大小写
audAudience(s),令牌的受众,分大小写的字符串数组
expExpiration time令牌的过期时间戳。超过此时间的token会作废 该声明是一个整数是1970年1月1日以来的秒数
iat: 令牌的颁发时间该声明是一个整数是1970年1月1日以来的秒数
jti: 令牌的唯一标识,该声明的值在令牌颁发者创建的每一个令牌中都是唯一的,为了防止冲突,它通常是一个密码学随机值。这个值相当于向结构化令牌中加入了一个攻击者无法获得的随机熵组件,有利于防止令牌猜测攻击和重放攻击。
```
也可以新增用户系统需要使用的自定义字段比如下面的例子添加了name 用户昵称:
```js
{
"sub": "1234567890",
"name": "John Doe"
}
```
然后将其进行Base64编码得到JWT的第二部分
`JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE`
**Signature**
这个部分需要Base64编码后的Header和Base64编码后的Payload使用 . 连接组成的字符串然后通过Header中声明的加密方式进行加密$secret 表示用户的私钥然后就构成了jwt的第三部分。
```js
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');
```
将这三部分用 . 连接成一个完整的字符串,就构成了 1.3.2 节最开始的JWT示例。
#### 1.3.3 时效
网关会验证token中的exp字段一旦这个字段过期了网关会认为这个token无效而将请求直接打回。过期时间这个值必须设置。
#### 1.3.4 JWT的几个特点
1. JWT 默认是不加密,不能将秘密数据写入 JWT。
2. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT可以降低服务器查询数据库的次数。
3. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
4. JWT 本身包含了认证信息一旦泄露任何人都可以获得该令牌的所有权限。为了减少盗用JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
5. 为了减少盗用JWT 不应该使用 HTTP 协议明码传输要使用HTTPS 协议传输。
## 2、用户系统如何应用JWT插件保护API
### 2.1 生成一对JWKJSON Web 密钥)
**方法一、在线生成:**
用户可以在这个站点https://mkjwk.org 生成用于token生成与验证的私钥与公钥 私钥用于授权服务签发JWT公钥配置到JWT插件中用于网关对请求验签注意网关使用的jwks格式配置下图中Public Key需要放到keys结构体中`{"keys":[{"kty":"RSA","e":"AQAB",...}]}`
<img src="https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/2336348951/p135823.png" style="zoom:50%" />
**方法二、本地生成:**
本文应用Java示例说明其他语言用户也可以找到相关的工具生成密钥对。 新建一个Maven项目加入如下依赖
```xml
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.0</version>
</dependency>
```
使用如下的代码生成一对RSA密钥
```java
RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
final String publicKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
final String privateKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
```
### 2.2 使用JWK中的私钥实现颁发token 的认证服务
需要使用2.1节中在线生成的 Keypair JSON字符串三个方框内的第一个或者本地生成的 privateKeyString JSON字符串作为私钥来颁发token用于授权可信的用户访问受保护的API具体实现可以参考下方示例。 向客户颁发token的形式由用户根据具体的业务场景决定可以将颁发token的功能部署到生产环境配置成普通API后由访问者通过用户名密码获得也可以直接在本地环境生成token 后,直接拷贝给指定用户使用。
```java
import java.security.PrivateKey;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.lang.JoseException;
public class GenerateJwtDemo {
public static void main(String[] args) throws JoseException {
String keyId = "uniq_key";
//使用本文2.1节生成的Keypair
String privateKeyJson = "{\n"
+ " \"kty\": \"RSA\",\n"
+ " \"d\": "
+
"\"O9MJSOgcjjiVMNJ4jmBAh0mRHF_TlaVva70Imghtlgwxl8BLfcf1S8ueN1PD7xV6Cnq8YenSKsfiNOhC6yZ_fjW1syn5raWfj68eR7cjHWjLOvKjwVY33GBPNOvspNhVAFzeqfWneRTBbga53Agb6jjN0SUcZdJgnelzz5JNdOGaLzhacjH6YPJKpbuzCQYPkWtoZHDqWTzCSb4mJ3n0NRTsWy7Pm8LwG_Fd3pACl7JIY38IanPQDLoighFfo-Lriv5z3IdlhwbPnx0tk9sBwQBTRdZ8JkqqYkxUiB06phwr7mAnKEpQJ6HvhZBQ1cCnYZ_nIlrX9-I7qomrlE1UoQ\",\n"
+ " \"e\": \"AQAB\",\n"
+ " \"alg\": \"RS256\",\n"
+ " \"n\": \"vCuB8MgwPZfziMSytEbBoOEwxsG7XI3MaVMoocziP4SjzU4IuWuE_DodbOHQwb_thUru57_Efe"
+
"--sfATHEa0Odv5ny3QbByqsvjyeHk6ZE4mSAV9BsHYa6GWAgEZtnDceeeDc0y76utXK2XHhC1Pysi2KG8KAzqDa099Yh7s31AyoueoMnrYTmWfEyDsQL_OAIiwgXakkS5U8QyXmWicCwXntDzkIMh8MjfPskesyli0XQD1AmCXVV3h2Opm1Amx0ggSOOiINUR5YRD6mKo49_cN-nrJWjtwSouqDdxHYP-4c7epuTcdS6kQHiQERBd1ejdpAxV4c0t0FHF7MOy9kw\"\n"
+ "}";
JwtClaims claims = new JwtClaims();
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
//过期时间一定要设置
NumericDate date = NumericDate.now();
date.addSeconds(120*60);
claims.setExpirationTime(date);
claims.setNotBeforeMinutesInThePast(1);
claims.setSubject("YOUR_SUBJECT");
claims.setAudience("YOUR_AUDIENCE");
//添加自定义参数所有值请都使用String类型
claims.setClaim("userId", "1213234");
claims.setClaim("email", "userEmail@youapp.com");
JsonWebSignature jws = new JsonWebSignature();
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
jws.setKeyIdHeaderValue(keyId);
jws.setPayload(claims.toJson());
PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey();
jws.setKey(privateKey);
String jwtResult = jws.getCompactSerialization();
System.out.println("Generate Json Web token , result is " + jwtResult);
}
}
```
# 插件配置说明
## 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------------------- | ----------------- | -------- | ------------------------------------------------- | ------------------------ |
| `name` | string | 必填 | - | 配置该consumer的名称 |
| `jwks` | string | 必填 | - | https://www.rfc-editor.org/rfc/rfc7517 指定的json格式字符串是由验证JWT中签名的公钥或对称密钥组成的Json Web Key Set |
| `issuer` | string | 必填 | - | JWT的签发者需要和payload中的iss字段保持一致 |
| `claims_to_headers` | array of object | 选填 | - | 抽取JWT的payload中指定字段设置到指定的请求头中转发给后端 |
| `from_headers` | array of object | 选填 | {"name":"Authorization","value_prefix":"Bearer "} | 从指定的请求头中抽取JWT |
| `from_params` | array of string | 选填 | access_token | 从指定的URL参数中抽取JWT |
| `from_cookies` | array of string | 选填 | - | 从指定的cookie中抽取JWT |
| `clock_skew_seconds` | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量单位为秒 |
| `keep_token` | bool | 选填 | ture | 转发给后端时是否保留JWT |
**注意:**
- 只有当`from_headers`,`from_params`,`from_cookies`均未配置时,才会使用默认值
`from_headers` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
| `name` | string | 必填 | - | 抽取JWT的请求header |
| `value_prefix` | string | 必填 | - | 对请求header的value去除此前缀剩余部分作为JWT |
`claims_to_headers` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求| 默认值 | 描述 |
| ---------------- | --------------- | ------- | ------ | --------------------------------------------------------- |
| `claim` | string | 必填 | - | JWT payload中的指定字段要求必须是字符串或无符号整数类型 |
| `header` | string | 必填 | - | 从payload取出字段的值设置到这个请求头中转发给后端 |
| `override` | bool | 选填 | true | true时存在同名请求头会进行覆盖false时追加同名请求头 |
`_rules_` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `_match_route_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
| `_match_domain_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的域名 |
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求配置允许访问的consumer名称 |
**注意:**
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
## 配置示例
### 对特定路由或域名开启
以下配置将对网关特定路由或域名开启 Jwt Auth 认证和鉴权注意如果一个JWT能匹配多个`jwks`,则按照配置顺序命中第一个匹配的`consumer`
```yaml
consumers:
- name: consumer1
issuer: abcd
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
- name: consumer2
issuer: abc
jwks: |
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "123",
"alg": "RS256",
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
}
]
}
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
allow:
- consumer1
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
allow:
- consumer2
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name``consumer1`的调用者访问,其他调用者不允许访问;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name``consumer2`的调用者访问,其他调用者不允许访问。
#### 根据该配置,下列请求可以允许访问:
假设以下请求会匹配到route-a这条路由
**将 JWT 设置在 url 参数中**
```bash
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
```
**将 JWT 设置在 http 请求头中**
```bash
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
```
认证鉴权通过后请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
#### 下列请求将拒绝访问:
**请求未提供JWT返回401**
```bash
curl http://xxx.hello.com/test
```
**根据请求提供的JWT匹配到的调用者无访问权限返回403**
```bash
# consumer1不在*.example.com的allow列表里
curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
```
### 网关实例级别开启
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 JWT Auth 认证
```yaml
consumers:
- name: consumer1
issuer: abcd
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
- name: consumer2
issuer: abc
jwks: |
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "123",
"alg": "RS256",
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
}
]
}
```
# 常见错误码说明
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- | ---------------------- | -------------------------------------------------------------------------------- |
| 401 | Jwt missing | 请求头未提供JWT |
| 401 | Jwt expired | JWT已经过期 |
| 401 | Jwt verification fails | JWT payload校验失败如iss不匹配 |
| 403 | Access Denied | 无权限访问当前路由 |

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,121 @@
# 功能说明
`key-auth`插件实现了基于 API Key 进行认证鉴权的功能,支持从 HTTP 请求的 URL 参数或者请求头解析 API Key同时验证该 API Key 是否有权限访问。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | ------------------------------------------- | ------ | ----------------------------------------------------------- |
| `consumers` | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
| `keys` | array of string | 必填 | - | API Key 的来源字段名称,可以是 URL 参数或者 HTTP 请求头名称 |
| `in_query` | bool | `in_query``in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 URL 参数中解析 API Key |
| `in_header` | bool | `in_query``in_header` 至少有一个为 true | true | 配置 true 时,网关会尝试从 HTTP 请求头中解析 API Key |
| `_rules_` | array of object | 选填 | - | 配置特定路由或域名的访问权限列表,用于对请求进行鉴权 |
`consumers`中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ------------ | -------- | -------- | ------ | ------------------------ |
| `credential` | string | 必填 | - | 配置该consumer的访问凭证 |
| `name` | string | 必填 | - | 配置该consumer的名称 |
`_rules_` 中每一项的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ---------------- | --------------- | ------------------------------------------------- | ------ | -------------------------------------------------- |
| `_match_route_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的路由名称 |
| `_match_domain_` | array of string | 选填,`_match_route_``_match_domain_`中选填一项 | - | 配置要匹配的域名 |
| `allow` | array of string | 必填 | - | 对于符合匹配条件的请求配置允许访问的consumer名称 |
**注意:**
- 若不配置`_rules_`字段,则默认对当前网关实例的所有路由开启认证;
- 对于通过认证鉴权的请求请求的header会被添加一个`X-Mse-Consumer`字段,用以标识调用者的名称。
# 配置示例
## 对特定路由或域名开启
以下配置将对网关特定路由或域名开启 Key Auth 认证和鉴权,注意`credential`字段不能重复
```yaml
consumers:
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
name: consumer1
- credential: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
name: consumer2
keys:
- apikey
- x-api-key
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
allow:
- consumer1
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
allow:
- consumer2
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许`name``consumer1`的调用者访问,其他调用者不允许访问;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将允许`name``consumer2`的调用者访问,其他调用者不允许访问。
### 根据该配置,下列请求可以允许访问:
假设以下请求会匹配到route-a这条路由
**将 API Key 设置在 url 参数中**
```bash
curl http://xxx.hello.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
```
**将 API Key 设置在 http 请求头中**
```bash
curl http://xxx.hello.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
```
认证鉴权通过后请求的header中会被添加一个`X-Mse-Consumer`字段,在此例中其值为`consumer1`,用以标识调用方的名称
### 下列请求将拒绝访问:
**请求未提供 API Key返回401**
```bash
curl http://xxx.hello.com/test
```
**请求提供的 API Key 无权访问返回401**
```bash
curl http://xxx.hello.com/test?apikey=926d90ac-ba2e-11ec-ab68-00163e1250b5
```
**根据请求提供的 API Key匹配到的调用者无访问权限返回403**
```bash
# consumer2不在route-a的allow列表里
curl http://xxx.hello.com/test?apikey=c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
```
## 网关实例级别开启
以下配置未指定`_rules_`字段,因此将对网关实例级别开启 Key Auth 认证
```yaml
consumers:
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
name: consumer1
- credential: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
name: consumer2
keys:
- apikey
- x-api-key
```
# 相关错误码
| HTTP 状态码 | 出错信息 | 原因说明 |
| ----------- | --------------------------------------------------------- | ----------------------- |
| 401 | No API key found in request | 请求未提供 API Key |
| 401 | Request denied by Key Auth check. Invalid API key | 不允许当前 API Key 访问 |
| 403 | Request denied by Basic Auth check. Unauthorized consumer | 请求的调用方无访问权限 |

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,68 @@
# 功能说明
`key-rate-limit`插件实现了基于特定键值实现限流,键值来源可以是 URL 参数、HTTP 请求头
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| limit_by_header | string | 选填,`limit_by_header`,`limit_by_param` 中选填一项 | - | 配置获取限流键值的来源 http 请求头名称 |
| limit_by_param | string | 选填,`limit_by_header`,`limit_by_param` 中选填一项 | - | 配置获取限流键值的来源 URL 参数名称 |
| limit_keys | array of object | 必填 | - | 配置匹配键值后的限流次数 |
`limit_keys`中每一项的配置字段说明
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| key | string | 必填 | - | 匹配的键值 |
| query_per_second | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每秒请求次数 |
| query_per_minute | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每分钟请求次数 |
| query_per_hour | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每小时请求次数 |
| query_per_day | number | 选填,`query_per_second`,`query_per_minute`,`query_per_hour`,`query_per_day` 中选填一项 | - | 允许每天请求次数 |
# 配置示例
## 识别请求参数 apikey进行区别限流
```yaml
limit_by_param: apikey
limit_keys:
- key: 9a342114-ba8a-11ec-b1bf-00163e1250b5
query_per_second: 10
- key: a6a6d7f2-ba8a-11ec-bec2-00163e1250b5
query_per_minute: 100
```
## 识别请求头 x-ca-key进行区别限流
```yaml
limit_by_header: x-ca-key
limit_keys:
- key: 102234
query_per_second: 10
- key: 308239
query_per_hour: 10
```
## 对特定路由或域名开启
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
limit_by_header: x-ca-key
limit_keys:
- key: 102234
query_per_second: 10
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
limit_by_header: x-ca-key
limit_keys:
- key: 102234
query_per_second: 100
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,86 @@
# 功能说明
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
| block_bodys | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
# 配置示例
## 屏蔽请求 url 路径
```yaml
block_urls:
- swagger.html
- foo=bar
case_sensitive: false
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://example.com?foo=Bar
curl http://exmaple.com/Swagger.html
```
## 屏蔽请求 header
```yaml
block_headers:
- example-key
- example-value
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://example.com -H 'example-key: 123'
curl http://exmaple.com -H 'my-header: example-value'
```
## 屏蔽请求 body
```yaml
block_bodys:
- "hello world"
case_sensitive: false
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://example.com -d 'Hello World'
curl http://exmaple.com -d 'hello world'
```
## 对特定路由或域名开启
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
block_bodys:
- "hello world"
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
block_urls:
- "swagger.html"
block_bodys:
- "hello world"
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
# 请求 Body 大小限制
当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls``block_headers` 项时,不会对该请求执行屏蔽操作
当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits将返回 `413 Payload Too Large`

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,11 @@
# 功能说明
`http2-misdirect`插件用于解决网关开启 HTTP2 时,因为浏览器复用连接导致访问出现 404 等问题。
# 插件原理
HTTP2 协议允许两个不同域名的请求,在域名解析到相同 IP并且使用了相同证书的情况下复用同一条连接。这在一些特殊场景会导致复用连接的请求发送给了错误的 Virtual Host 进行处理,从而导致出现 404 等问题。
本插件基于`HTTP/2 RFC 7540``9.1.1``9.1.2`章节描述,在发现请求 SNI 与当前 Virtual Host 不匹配时,发送 HTTP 421 状态码,强制浏览器新建连接,并根据当前请求域名生成匹配的 SNI从而让网关能正确处理路由。
# 浏览器兼容性
`Safari` 浏览器 `15.1` 版本以下不支持 HTTP 421 状态码,若有此类客户端访问场景,建议对相应域名关闭 HTTP2 的 ALPN

View File

@@ -0,0 +1 @@
1.0.0