diff --git a/apisix/admin/consumers.lua b/apisix/admin/consumers.lua index feaa7080b2a5..14bd5cbe0d7b 100644 --- a/apisix/admin/consumers.lua +++ b/apisix/admin/consumers.lua @@ -49,9 +49,6 @@ local function check_conf(conf) local plugin_obj = plugin.get(name) if plugin_obj.type == 'auth' then count_auth_plugin = count_auth_plugin + 1 - if count_auth_plugin > 1 then - return nil, {error_msg = "only one auth plugin is allowed"} - end end end diff --git a/apisix/consumer.lua b/apisix/consumer.lua index a6315440caee..3163e291334c 100644 --- a/apisix/consumer.lua +++ b/apisix/consumer.lua @@ -57,8 +57,6 @@ local function plugin_consumer() new_consumer.auth_conf = config core.log.info("consumer:", core.json.delay_encode(new_consumer)) core.table.insert(plugins[name].nodes, new_consumer) - - break end end diff --git a/apisix/init.lua b/apisix/init.lua index 7333dd14936f..411cdd4f7e2e 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -514,6 +514,10 @@ function _M.http_access_phase() api_ctx.consumer, api_ctx ) + + core.log.info("find consumer ", api_ctx.consumer.username, + ", config changed: ", changed) + if changed then core.table.clear(api_ctx.plugins) api_ctx.plugins = plugin.filter(route, api_ctx.plugins) diff --git a/doc/admin-api.md b/doc/admin-api.md index 51e70c50c869..ba23f7e3b2b3 100644 --- a/doc/admin-api.md +++ b/doc/admin-api.md @@ -450,7 +450,7 @@ Config Example: } ``` -The binding authentication and authorization plug-in is a bit special. When it needs to be used in conjunction with the consumer, it needs to provide user name, password and other information; on the other hand, when it is bound with route / service, it does not require any parameters. Because at this time, it is based on the user request data to infer which consumer the user corresponds to. +The binding authentication plug-in is a bit special. When it needs to be used in conjunction with the consumer, it needs to provide user name, password and other information; on the other hand, when it is bound with route / service, it does not require any parameters. Because at this time, it is based on the user request data to infer which consumer the user corresponds to. Example: @@ -476,6 +476,8 @@ Date: Thu, 26 Dec 2019 08:17:49 GMT {"node":{"value":{"username":"jack","plugins":{"key-auth":{"key":"auth-one"},"limit-count":{"time_window":60,"count":2,"rejected_code":503,"key":"remote_addr","policy":"local"}}},"createdIndex":64,"key":"\/apisix\/consumers\/jack","modifiedIndex":64},"prevNode":{"value":"{\"username\":\"jack\",\"plugins\":{\"key-auth\":{\"key\":\"auth-one\"},\"limit-count\":{\"time_window\":60,\"count\":2,\"rejected_code\":503,\"key\":\"remote_addr\",\"policy\":\"local\"}}}","createdIndex":63,"key":"\/apisix\/consumers\/jack","modifiedIndex":63},"action":"set"} ``` +Since `v2.2`, we can bind multiple authentication plugins to the same consumer. + > Response Parameters Return response from etcd currently. diff --git a/doc/zh-cn/admin-api.md b/doc/zh-cn/admin-api.md index 5dae7d591022..1e7cc4e0b698 100644 --- a/doc/zh-cn/admin-api.md +++ b/doc/zh-cn/admin-api.md @@ -463,7 +463,7 @@ consumer 对象 json 配置内容: } ``` -绑定认证授权插件有些特别,当它需要与 consumer 联合使用时,需要提供用户名、密码等信息;另一方面,当它与 route/service 绑定时,是不需要任何参数的。因为这时候是根据用户请求数据来反向推出用户对应的是哪个 consumer +绑定认证插件有些特别,当它需要与 consumer 联合使用时,需要提供用户名、密码等信息;另一方面,当它与 route/service 绑定时,是不需要任何参数的。因为这时候是根据用户请求数据来反向推出用户对应的是哪个 consumer 示例: @@ -491,6 +491,8 @@ Date: Thu, 26 Dec 2019 08:17:49 GMT {"node":{"value":{"username":"jack","plugins":{"key-auth":{"key":"auth-one"},"limit-count":{"time_window":60,"count":2,"rejected_code":503,"key":"remote_addr","policy":"local"}}},"createdIndex":64,"key":"\/apisix\/consumers\/jack","modifiedIndex":64},"prevNode":{"value":"{\"username\":\"jack\",\"plugins\":{\"key-auth\":{\"key\":\"auth-one\"},\"limit-count\":{\"time_window\":60,\"count\":2,\"rejected_code\":503,\"key\":\"remote_addr\",\"policy\":\"local\"}}}","createdIndex":63,"key":"\/apisix\/consumers\/jack","modifiedIndex":63},"action":"set"} ``` +从 `v2.2` 版本之后,同一个 consumer 可以绑定多个认证插件。 + > 应答参数 目前是直接返回与 etcd 交互后的结果。 diff --git a/t/config-center-yaml/stream-route.t b/t/config-center-yaml/stream-route.t index 204679783ea4..77d2dd90714d 100644 --- a/t/config-center-yaml/stream-route.t +++ b/t/config-center-yaml/stream-route.t @@ -48,7 +48,6 @@ run_tests(); __DATA__ - === TEST 1: sanity --- apisix_yaml stream_routes: diff --git a/t/node/consumer-plugin.t b/t/node/consumer-plugin.t index 2748b01de3f4..2764080011cb 100644 --- a/t/node/consumer-plugin.t +++ b/t/node/consumer-plugin.t @@ -151,47 +151,7 @@ apikey: auth-one -=== TEST 6: two auth plugins (not allow) ---- 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": "jack", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - }, - "key-auth": { - "key": "auth-one" - }, - "jwt-auth": { - "key": "auth-one" - } - } - }]] - ) - - ngx.status = code - ngx.print(body) - } - } ---- request -GET /t ---- error_code: 400 ---- response_body -{"error_msg":"only one auth plugin is allowed"} ---- no_error_log -[error] - - - -=== TEST 7: missing auth plugins (not allow) +=== TEST 6: missing auth plugins (not allow) --- config location /t { content_by_lua_block { @@ -225,7 +185,7 @@ GET /t -=== TEST 8: use the new configuration after the consumer's configuration is updated +=== TEST 7: use the new configuration after the consumer's configuration is updated --- config location /t { content_by_lua_block { @@ -289,3 +249,195 @@ GET /t {"200":4,"503":1} --- no_error_log [error] + + + +=== TEST 8: consumer with multiple auth plugins +--- 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": "John_Doe", + "desc": "new consumer", + "plugins": { + "key-auth": { + "key": "consumer-plugin-John_Doe" + }, + "hmac-auth": { + "access_key": "my-access-key", + "secret_key": "my-secret-key", + "clock_skew": 1 + } + } + }]], + [[{ + "node": { + "value": { + "username": "John_Doe", + "desc": "new consumer", + "plugins": { + "key-auth": { + "key": "consumer-plugin-John_Doe" + }, + "hmac-auth": { + "access_key": "my-access-key", + "secret_key": "my-secret-key", + "clock_skew": 1 + } + } + } + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 9: bind to routes +--- 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" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.log(ngx.ERR, "failed to bind route 1") + ngx.status = code + ngx.say(body) + return + end + + local code, body = t('/apisix/admin/routes/2', + ngx.HTTP_PUT, + [[{ + "plugins": { + "hmac-auth": {} + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/status" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 10: hit consumer, key-auth +--- request +GET /hello +--- more_headers +apikey: consumer-plugin-John_Doe +--- response_body +hello world +--- error_log +find consumer John_Doe +--- no_error_log +[error] + + + +=== TEST 11: hit consumer, hmac-auth +--- config +location /t { + content_by_lua_block { + local ngx_time = ngx.time + local ngx_http_time = ngx.http_time + local core = require("apisix.core") + local t = require("lib.test_admin") + local hmac = require("resty.hmac") + local ngx_encode_base64 = ngx.encode_base64 + + local secret_key = "my-secret-key" + local timestamp = ngx_time() + local gmt = ngx_http_time(timestamp) + local access_key = "my-access-key" + local custom_header_a = "asld$%dfasf" + local custom_header_b = "23879fmsldfk" + + local signing_string = { + "GET", + "/status", + "", + access_key, + gmt, + "x-custom-header-a:" .. custom_header_a, + "x-custom-header-b:" .. custom_header_b + } + signing_string = core.table.concat(signing_string, "\n") .. "\n" + core.log.info("signing_string:", signing_string) + + local signature = hmac:new(secret_key, hmac.ALGOS.SHA256):final(signing_string) + core.log.info("signature:", ngx_encode_base64(signature)) + local headers = {} + headers["X-HMAC-SIGNATURE"] = ngx_encode_base64(signature) + headers["X-HMAC-ALGORITHM"] = "hmac-sha256" + headers["Date"] = gmt + headers["X-HMAC-ACCESS-KEY"] = access_key + headers["X-HMAC-SIGNED-HEADERS"] = "x-custom-header-a;x-custom-header-b" + headers["x-custom-header-a"] = custom_header_a + headers["x-custom-header-b"] = custom_header_b + + local code, body = t.test('/status', + ngx.HTTP_GET, + nil, + nil, + headers + ) + + ngx.status = code + ngx.say(body) + } +} +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] +--- error_log +find consumer John_Doe