feature: support consumer_name as key for limit-req plugin. (#2270)

fix #2267
This commit is contained in:
Firstsawyou 2020-10-06 18:18:22 +08:00 committed by GitHub
parent 720d135fd2
commit c3de84e285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 524 additions and 27 deletions

View File

@ -27,7 +27,7 @@ local schema = {
burst = {type = "number", minimum = 0},
key = {type = "string",
enum = {"remote_addr", "server_addr", "http_x_real_ip",
"http_x_forwarded_for"},
"http_x_forwarded_for", "consumer_name"},
},
rejected_code = {type = "integer", minimum = 200, default = 503},
},
@ -67,7 +67,17 @@ function _M.access(conf, ctx)
return 500
end
local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
local key
if conf.key == "consumer_name" then
if not ctx.consumer_id then
core.log.error("consumer not found.")
return 500, { message = "Consumer not found."}
end
key = ctx.consumer_id .. ctx.conf_type .. ctx.conf_version
else
key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
end
core.log.info("limit key: ", key)
local delay, err = lim:incoming(key, true)

View File

@ -20,14 +20,14 @@
- [中文](../zh-cn/plugins/limit-req.md)
# Summary
- [Introduction](#introduction)
- [Attributes](#attributes)
- [Example](#example)
- [How to enable on the `route` or `serivce`](#how-to-enable-on-the-route-or-serivce)
- [How to enable on the `consumer`](#how-to-enable-on-the-consumer)
- [Disable Plugin](#disable-plugin)
- [**Name**](#name)
- [**Attributes**](#attributes)
- [**How To Enable**](#how-to-enable)
- [**Test Plugin**](#test-plugin)
- [**Disable Plugin**](#disable-plugin)
## Name
## Introduction
limit request rate using the "leaky bucket" method.
@ -37,14 +37,16 @@ limit request rate using the "leaky bucket" method.
| ------------- | ------- | ----------- | ------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | required | | [0,...] | the specified request rate (number per second) threshold. Requests exceeding this rate (and below `burst`) will get delayed to conform to the rate. |
| burst | integer | required | | [0,...] | the number of excessive requests per second allowed to be delayed. Requests exceeding this hard limit will get rejected immediately. |
| key | string | required | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"] | the user specified key to limit the rate, now accept those as key: "remote_addr"(client's IP), "server_addr"(server's IP), "X-Forwarded-For/X-Real-IP" in request header. |
| rejected_code | string | optional | 503 | [200,...] | The HTTP status code returned when the request exceeds the threshold is rejected. The default is 503. |
| key | string | required | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | the user specified key to limit the rate, now accept those as key: "remote_addr"(client's IP), "server_addr"(server's IP), "X-Forwarded-For/X-Real-IP" in request header, "consumer_name"(consumer's username). |
| rejected_code | integer | optional | 503 | [200,...] | The HTTP status code returned when the request exceeds the threshold is rejected. |
**Key can be customized by the user, only need to modify a line of code of the plug-in to complete. It is a security consideration that is not open in the plugin.**
## How To Enable
## Example
Here's an example, enable the limit req plugin on the specified route:
### How to enable on the `route` or `serivce`
Take `route` as an example (the use of `service` is the same method), enable the `limit-req` plugin on the specified route.
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@ -76,7 +78,7 @@ Then add limit-req plugin:
![add plugin](../images/plugin/limit-req-2.png)
## Test Plugin
**Test Plugin**
The above configuration limits the request rate to 1 per second. If it is greater than 1 and less than 3, the delay will be added. If the rate exceeds 3, it will be rejected:
@ -104,6 +106,78 @@ Server: APISIX web server
This means that the limit req plugin is in effect.
### How to enable on the `consumer`
To enable the `limit-req` plugin on the consumer, it needs to be used together with the authorization plugin. Here, the key-auth authorization plugin is taken as an example.
1. Bind the `limit-req` plugin to the consumer
```shell
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "consumer_jack",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 1,
"burst": 1,
"rejected_code": 403,
"key": "consumer_name"
}
}
}'
```
2. Create a `route` and enable the `key-auth` plugin
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"methods": ["GET"],
"uri": "/index.html",
"plugins": {
"key-auth": {
"key": "auth-jack"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
**Test Plugin**
The value of `rate + burst` is not exceeded.
```shell
curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
HTTP/1.1 200 OK
......
```
When the value of `rate + burst` is exceeded.
```shell
curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
HTTP/1.1 403 Forbidden
.....
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
Explains that the `limit-req` plugin tied to `consumer` has taken effect.
## Disable Plugin
When you want to disable the limit req plugin, it is very simple,
@ -127,4 +201,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
}'
```
Remove the `limit-req` plugin on `consumer`.
```shell
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "consumer_jack",
"plugins": {
"key-auth": {
"key": "auth-jack"
}
}
}'
```
The limit req plugin has been disabled now. It works for other plugins.

View File

@ -19,26 +19,34 @@
- [English](../../plugins/limit-req.md)
# limit-req
# 目录
- [简介](#简介)
- [属性](#属性)
- [示例](#示例)
- [如何在 `route` 或 `service` 上使用](#如何在`route`或`service`上使用)
- [如何在 `consumer` 上使用](#如何在`consumer`上使用)
- [移除插件](#移除插件)
## 简介
限制请求速度的插件,使用的是漏桶算法。
## 参数
## 属性
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
| ------------- | ------- | ------ | ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| rate | integer | 必须 | | [0,...] | 指定的请求速率(以秒为单位),请求速率超过 `rate` 但没有超过 `rate` + `brust`)的请求会被加上延时。 |
| burst | integer | 必须 | | [0,...] | t请求速率超过 `rate` + `brust`)的请求会被直接拒绝。 |
| key | string | 必须 | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP"。 |
| rejected_code | string | 可选 | 503 | [200,...] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码 |
| key | string | 必须 | | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP""consumer_name"(consumer 的 username)。 |
| rejected_code | integer | 可选 | 503 | [200,...] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码 |
**key 是可以被用户自定义的,只需要修改插件的一行代码即可完成。并没有在插件中放开是处于安全的考虑。**
## 示例
### 开启插件
### 如何在`route`或`service`上使用
下面是一个示例,在指定的 route 上开启了 limit req 插件:
这里以`route`为例(`service`的使用是同样的方法),在指定的 `route` 上启用 `limit-req` 插件。
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@ -70,7 +78,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
![添加插件](../../images/plugin/limit-req-2.png)
### 测试插件
**测试插件**
上述配置限制了每秒请求速率为 1大于 1 小于 3 的会被加上延时,速率超过 3 就会被拒绝:
@ -98,7 +106,79 @@ Server: APISIX web server
这就表示 limit req 插件生效了。
### 移除插件
### 如何在`consumer`上使用
consumer上开启`limit-req`插件需要与授权插件一起配合使用这里以key-auth授权插件为例。
1、将`limit-req`插件绑定到consumer上
```shell
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "consumer_jack",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 1,
"burst": 1,
"rejected_code": 403,
"key": "consumer_name"
}
}
}'
```
2、创建`route`并开启`key-auth`插件
```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"methods": ["GET"],
"uri": "/index.html",
"plugins": {
"key-auth": {
"key": "auth-jack"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
**测试插件**
未超过`rate + burst` 的值
```shell
curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
HTTP/1.1 200 OK
......
```
当超过`rate + burst` 的值
```shell
curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
HTTP/1.1 403 Forbidden
.....
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
说明绑在`consumer`上的 `limit-req`插件生效了
## 移除插件
当你想去掉 limit req 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效:
@ -116,4 +196,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
}'
```
移除`consumer`上的 `limit-req` 插件
```shell
curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "consumer_jack",
"plugins": {
"key-auth": {
"key": "auth-jack"
}
}
}'
```
现在就已经移除了 limit req 插件了。其他插件的开启和移除也是同样的方法。

View File

@ -51,7 +51,7 @@ GET /apisix/admin/plugins
--- request
GET /apisix/admin/plugins/limit-req
--- response_body
{"properties":{"rate":{"minimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200}},"required":["rate","burst","key"],"type":"object"}
{"properties":{"rate":{"minimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for","consumer_name"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200}},"required":["rate","burst","key"],"type":"object"}
--- no_error_log
[error]

View File

@ -102,7 +102,7 @@ done
},
"type": "roundrobin"
},
"desc": "上游节点",
"desc": "upstream_node",
"uri": "/hello"
}]],
[[{
@ -122,7 +122,7 @@ done
},
"type": "roundrobin"
},
"desc": "上游节点",
"desc": "upstream_node",
"uri": "/hello"
},
"key": "/apisix/routes/1"
@ -362,7 +362,7 @@ passed
},
"type": "roundrobin"
},
"desc": "上游节点",
"desc": "upstream_node",
"uri": "/hello"
}]]
)
@ -403,7 +403,7 @@ passed
},
"type": "roundrobin"
},
"desc": "上游节点",
"desc": "upstream_node",
"uri": "/hello"
}]],
[[{
@ -432,3 +432,308 @@ GET /t
passed
--- no_error_log
[error]
=== TEST 12: consumer binds the limit-req plugin and `key` is `consumer_name`
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 3,
"burst": 2,
"rejected_code": 403,
"key": "consumer_name"
}
}
}]],
[[{
"node": {
"value": {
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 3,
"burst": 2,
"rejected_code": 403,
"key": "consumer_name"
}
}
}
},
"action": "set"
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 13: route add "key-auth" plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"key-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "upstream_node",
"uri": "/hello"
}]],
[[{
"node": {
"value": {
"plugins": {
"key-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "upstream_node",
"uri": "/hello"
},
"key": "/apisix/routes/1"
},
"action": "set"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 14: not exceeding the burst
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: auth-jack
--- error_code eval
[200, 200, 200]
--- no_error_log
[error]
=== TEST 15: update the limit-req plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 0.1,
"burst": 0.1,
"rejected_code": 403,
"key": "consumer_name"
}
}
}]],
[[{
"node": {
"value": {
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"limit-req": {
"rate": 0.1,
"burst": 0.1,
"rejected_code": 403,
"key": "consumer_name"
}
}
}
},
"action": "set"
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 16: exceeding the burst
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: auth-jack
--- error_code eval
[403, 403, 403, 403]
--- no_error_log
[error]
=== TEST 17: key is consumer_name
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"limit-req": {
"rate": 2,
"burst": 1,
"key": "consumer_name"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "upstream_node",
"uri": "/hello"
}]],
[[{
"node": {
"value": {
"plugins": {
"limit-req": {
"rate": 2,
"burst": 1,
"key": "consumer_name"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "upstream_node",
"uri": "/hello"
},
"key": "/apisix/routes/1"
},
"action": "set"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 18: get "consumer_name" is empty
--- request
GET /hello
--- error_code: 500
--- response_body
{"message":"Consumer not found."}
--- error_log
[error]
=== TEST 19: delete consumer
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers/new_consumer', ngx.HTTP_DELETE)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 20 delete route
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_DELETE)
ngx.status =code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]