Skip to content

Commit eed93f3

Browse files
authored
feat: add support for extra_headers in forward-auth plugin (#12405)
1 parent 53f74ee commit eed93f3

File tree

4 files changed

+321
-6
lines changed

4 files changed

+321
-6
lines changed

apisix/plugins/forward-auth.lua

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
-- limitations under the License.
1616
--
1717

18-
local ipairs = ipairs
19-
local core = require("apisix.core")
20-
local http = require("resty.http")
18+
local ipairs = ipairs
19+
local core = require("apisix.core")
20+
local http = require("resty.http")
21+
local pairs = pairs
22+
local type = type
23+
local tostring = tostring
2124

2225
local schema = {
2326
type = "object",
@@ -41,6 +44,20 @@ local schema = {
4144
items = {type = "string"},
4245
description = "client request header that will be sent to the authorization service"
4346
},
47+
extra_headers = {
48+
type = "object",
49+
minProperties = 1,
50+
patternProperties = {
51+
["^[^:]+$"] = {
52+
type = "string",
53+
description = "header value as a string; may contain variables"
54+
.. "like $remote_addr, $request_uri"
55+
}
56+
},
57+
description = "extra headers sent to the authorization service; "
58+
.. "values must be strings and can include variables"
59+
.. "like $remote_addr, $request_uri."
60+
},
4461
upstream_headers = {
4562
type = "array",
4663
default = {},
@@ -102,6 +119,22 @@ function _M.access(conf, ctx)
102119
auth_headers["Content-Encoding"] = core.request.header(ctx, "content-encoding")
103120
end
104121

122+
if conf.extra_headers then
123+
for header, value in pairs(conf.extra_headers) do
124+
if type(value) == "number" then
125+
value = tostring(value)
126+
end
127+
local resolve_value, err = core.utils.resolve_var(value, ctx.var)
128+
if not err then
129+
auth_headers[header] = resolve_value
130+
end
131+
if err then
132+
core.log.error("failed to resolve variable in extra header '",
133+
header, "': ",value,": ",err)
134+
end
135+
end
136+
end
137+
105138
-- append headers that need to be get from the client request header
106139
if #conf.request_headers > 0 then
107140
for _, header in ipairs(conf.request_headers) do

docs/en/latest/plugins/forward-auth.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ This Plugin moves the authentication and authorization logic to a dedicated exte
4040
| ----------------- | ------------- | -------- | ------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
4141
| uri | string | True | | | URI of the authorization service. |
4242
| ssl_verify | boolean | False | true | | When set to `true`, verifies the SSL certificate. |
43-
| request_method | string | False | GET | ["GET","POST"] | HTTP method for a client to send requests to the authorization service. When set to `POST` the request body is send to the authorization service. |
43+
| request_method | string | False | GET | ["GET","POST"] | HTTP method for a client to send requests to the authorization service. When set to `POST` the request body is sent to the authorization service. (not recommended - see section on [Using data from POST body](#using-data-from-post-body-to-make-decision-on-authorization-service)) |
4444
| request_headers | array[string] | False | | | Client request headers to be sent to the authorization service. If not set, only the headers provided by APISIX are sent (for example, `X-Forwarded-XXX`). |
45+
| extra_headers |object | False | | | Extra headers to be sent to the authorization service passed in key-value format. The value can be a variable like `$request_uri`, `$post_arg.xyz` |
4546
| upstream_headers | array[string] | False | | | Authorization service response headers to be forwarded to the Upstream. If not set, no headers are forwarded to the Upstream service. |
4647
| client_headers | array[string] | False | | | Authorization service response headers to be sent to the client when authorization fails. If not set, no headers will be sent to the client. |
4748
| timeout | integer | False | 3000ms | [1, 60000]ms | Timeout for the authorization service HTTP call. |
@@ -166,6 +167,110 @@ HTTP/1.1 403 Forbidden
166167
Location: http://example.com/auth
167168
```
168169

170+
### Using data from POST body to make decision on Authorization service
171+
172+
::: note
173+
When the decision is to be made on the basis of POST body, then it is recommended to use `$post_arg.*` with `extra_headers` field and make the decision on Authorization service on basis of headers rather than using POST `request_method` to pass the entire request body to Authorization service.
174+
:::
175+
176+
Create a serverless function on the `/auth` route that checks for the presence of the `tenant_id` header. If present, the route responds with HTTP 200 and sets the `X-User-ID` header to a fixed value `i-am-an-user`. If `tenant_id` is missing, it returns HTTP 400 with an error message.
177+
178+
```shell
179+
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \
180+
-H "X-API-KEY: $admin_key" \
181+
-H 'Content-Type: application/json' \
182+
-d '{
183+
"uri": "/auth",
184+
"plugins": {
185+
"serverless-pre-function": {
186+
"phase": "rewrite",
187+
"functions": [
188+
"return function(conf, ctx)
189+
local core = require(\"apisix.core\")
190+
if core.request.header(ctx, \"tenant_id\") then
191+
core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
192+
core.response.exit(200);
193+
else
194+
core.response.exit(400, \"tenant_id is required\")
195+
end
196+
end"
197+
]
198+
}
199+
}
200+
}'
201+
```
202+
203+
Create a route that accepts POST requests and uses the `forward-auth` plugin to call the auth endpoint with the `tenant_id` from the request. The request is forwarded to the upstream service only if the auth check returns 200.
204+
205+
```shell
206+
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
207+
-H "X-API-KEY: $admin_key" \
208+
-d '{
209+
"uri": "/post",
210+
"methods": ["POST"],
211+
"plugins": {
212+
"forward-auth": {
213+
"uri": "http://127.0.0.1:9080/auth",
214+
"request_method": "GET",
215+
"extra_headers": {"tenant_id": "$post_arg.tenant_id"}
216+
}
217+
},
218+
"upstream": {
219+
"nodes": {
220+
"httpbin.org:80": 1
221+
},
222+
"type": "roundrobin"
223+
}
224+
}'
225+
```
226+
227+
Send a POST request with the `tenant_id` header:
228+
229+
```shell
230+
curl -i http://127.0.0.1:9080/post -X POST -d '{
231+
"tenant_id": 123
232+
}'
233+
```
234+
235+
You should receive an `HTTP/1.1 200 OK` response similar to the following:
236+
237+
```json
238+
{
239+
"args": {},
240+
"data": "",
241+
"files": {},
242+
"form": {
243+
"{\n \"tenant_id\": 123\n}": ""
244+
},
245+
"headers": {
246+
"Accept": "*/*",
247+
"Content-Length": "23",
248+
"Content-Type": "application/x-www-form-urlencoded",
249+
"Host": "127.0.0.1",
250+
"User-Agent": "curl/8.13.0",
251+
"X-Amzn-Trace-Id": "Root=1-686b6e3f-2fdeff70183e71551f5c5729",
252+
"X-Forwarded-Host": "127.0.0.1"
253+
},
254+
"json": null,
255+
"origin": "127.0.0.1, 106.215.83.33",
256+
"url": "http://127.0.0.1/post"
257+
}
258+
```
259+
260+
Send a POST request without the `tenant_id` header:
261+
262+
```shell
263+
curl -i http://127.0.0.1:9080/post -X POST -d '{
264+
"abc": 123
265+
}'
266+
```
267+
268+
You should receive an `HTTP/1.1 400 Bad Request` response with the following message:
269+
270+
```shell
271+
tenant_id is required
272+
```
273+
169274
## Delete Plugin
170275

171276
To remove the `forward-auth` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.

docs/zh/latest/plugins/forward-auth.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ description: 本文介绍了关于 Apache APISIX `forward-auth` 插件的基本
3939
| ----------------- | ------------- | ------| ------- | -------------- | -------------------------------------------------------------------------------------------------------------------- |
4040
| uri | string || | | 设置 `authorization` 服务的地址 (例如:https://localhost:9188)。 |
4141
| ssl_verify | boolean || true | [true, false] | 当设置为 `true` 时,验证 SSL 证书。 |
42-
| request_method | string || GET | ["GET","POST"] | 客户端向 `authorization` 服务发送请求的方法。当设置为 POST 时,会将 `request body` 转发至 `authorization` 服务。 |
42+
| request_method | string || GET | ["GET","POST"] | 客户端向 authorization 服务发送请求的方法。当设置为 POST 时,会将 request body 转发至 authorization 服务。 |
4343
| request_headers | array[string] || | | 设置需要由客户端转发到 `authorization` 服务的请求头。如果没有设置,则只发送 APISIX 提供的 headers (例如:X-Forwarded-XXX)。 |
44+
| extra_headers |object | False | | | 以键值格式传递给授权服务的额外标头。值可以是变量,例如“$request_uri”或“$post_arg.xyz”。 |
4445
| upstream_headers | array[string] || | | 认证通过时,设置 `authorization` 服务转发至 `upstream` 的请求头。如果不设置则不转发任何请求头。 |
4546
| client_headers | array[string] || | | 认证失败时,由 `authorization` 服务向 `client` 发送的响应头。如果不设置则不转发任何响应头。 |
4647
| timeout | integer || 3000ms | [1, 60000]ms | `authorization` 服务请求超时时间。 |
@@ -168,6 +169,110 @@ HTTP/1.1 403 Forbidden
168169
Location: http://example.com/auth
169170
```
170171

172+
### Using data from POST body to make decision on Authorization service
173+
174+
::: note
175+
当要根据 POST 正文做出决定时,建议使用带有 `extra_headers` 字段的 `$post_arg.*` 并根据标头对授权服务做出决定,而不是使用 POST `request_method` 将整个请求正文传递给授权服务。
176+
:::
177+
178+
`/auth` 路由上创建一个无服务器函数,用于检查 `tenant_id` 标头是否存在。如果存在,路由会使用 HTTP 200 进行响应,并将 `X-User-ID` 标头设置为固定值 `i-am-an-user`。如果缺少 `tenant_id`,则会返回 HTTP 400 和错误消息。
179+
180+
```shell
181+
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \
182+
-H "X-API-KEY: $admin_key" \
183+
-H 'Content-Type: application/json' \
184+
-d '{
185+
"uri": "/auth",
186+
"plugins": {
187+
"serverless-pre-function": {
188+
"phase": "rewrite",
189+
"functions": [
190+
"return function(conf, ctx)
191+
local core = require(\"apisix.core\")
192+
if core.request.header(ctx, \"tenant_id\") then
193+
core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
194+
core.response.exit(200);
195+
else
196+
core.response.exit(400, \"tenant_id is required\")
197+
end
198+
end"
199+
]
200+
}
201+
}
202+
}'
203+
```
204+
205+
创建一个接受 POST 请求的路由,并使用 `forward-auth` 插件通过请求中的 `tenant_id` 调用身份验证端点。只有当身份验证检查返回 200 时,请求才会转发到上游服务。
206+
207+
```shell
208+
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
209+
-H "X-API-KEY: $admin_key" \
210+
-d '{
211+
"uri": "/post",
212+
"methods": ["POST"],
213+
"plugins": {
214+
"forward-auth": {
215+
"uri": "http://127.0.0.1:9080/auth",
216+
"request_method": "GET",
217+
"extra_headers": {"tenant_id": "$post_arg.tenant_id"}
218+
}
219+
},
220+
"upstream": {
221+
"nodes": {
222+
"httpbin.org:80": 1
223+
},
224+
"type": "roundrobin"
225+
}
226+
}'
227+
```
228+
229+
发送带有 `tenant_id` 标头的 POST 请求:
230+
231+
```shell
232+
curl -i http://127.0.0.1:9080/post -X POST -d '{
233+
"tenant_id": 123
234+
}'
235+
```
236+
237+
您应该收到类似以下内容的 `HTTP/1.1 200 OK` 响应:
238+
239+
```json
240+
{
241+
"args": {},
242+
"data": "",
243+
"files": {},
244+
"form": {
245+
"{\n \"tenant_id\": 123\n}": ""
246+
},
247+
"headers": {
248+
"Accept": "*/*",
249+
"Content-Length": "23",
250+
"Content-Type": "application/x-www-form-urlencoded",
251+
"Host": "127.0.0.1",
252+
"User-Agent": "curl/8.13.0",
253+
"X-Amzn-Trace-Id": "Root=1-686b6e3f-2fdeff70183e71551f5c5729",
254+
"X-Forwarded-Host": "127.0.0.1"
255+
},
256+
"json": null,
257+
"origin": "127.0.0.1, 106.215.83.33",
258+
"url": "http://127.0.0.1/post"
259+
}
260+
```
261+
262+
发送不带 `tenant_id` 标头的 POST 请求:
263+
264+
```shell
265+
curl -i http://127.0.0.1:9080/post -X POST -d '{
266+
"abc": 123
267+
}'
268+
```
269+
270+
您应该收到包含以下消息的 `HTTP/1.1 400 Bad Request` 响应:
271+
272+
```shell
273+
tenant_id is required
274+
```
275+
171276
## 删除插件
172277

173278
当你需要禁用 `forward-auth` 插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务:

0 commit comments

Comments
 (0)