This is an automated email from the ASF dual-hosted git repository.

wenming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b7b1de1f feat: add multi-auth plugin (#10482)
5b7b1de1f is described below

commit 5b7b1de1feeb9f6b5a57c93b13617d7f6dfa68c8
Author: Madhawa Gunasekara <[email protected]>
AuthorDate: Mon Nov 20 02:36:02 2023 +0100

    feat: add multi-auth plugin (#10482)
    
    Co-authored-by: Madhawa Gunasekara <[email protected]>
---
 apisix/plugins/multi-auth.lua                      |  89 +++++
 conf/config-default.yaml                           |   1 +
 docs/en/latest/config.json                         |   3 +-
 .../latest/getting-started/key-authentication.md   |   1 +
 docs/en/latest/plugins/multi-auth.md               | 144 +++++++
 .../latest/getting-started/key-authentication.md   |   1 +
 t/admin/plugins.t                                  |   3 +-
 t/debug/debug-mode.t                               |   1 +
 t/plugin/multi-auth.t                              | 414 +++++++++++++++++++++
 9 files changed, 655 insertions(+), 2 deletions(-)

diff --git a/apisix/plugins/multi-auth.lua b/apisix/plugins/multi-auth.lua
new file mode 100644
index 000000000..5c6a82579
--- /dev/null
+++ b/apisix/plugins/multi-auth.lua
@@ -0,0 +1,89 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local require = require
+local pairs = pairs
+
+local schema = {
+    type = "object",
+    title = "work with route or service object",
+    properties = {
+        auth_plugins = { type = "array", minItems = 2 }
+    },
+    required = { "auth_plugins" },
+}
+
+
+local plugin_name = "multi-auth"
+
+local _M = {
+    version = 0.1,
+    priority = 2600,
+    type = 'auth',
+    name = plugin_name,
+    schema = schema
+}
+
+function _M.check_schema(conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then
+        return false, err
+    end
+
+    local auth_plugins = conf.auth_plugins
+    for k, auth_plugin in pairs(auth_plugins) do
+        for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do
+            local auth = require("apisix.plugins." .. auth_plugin_name)
+            if auth == nil then
+                return false, auth_plugin_name .. " plugin did not found"
+                else
+                if auth.type ~= 'auth' then
+                    return false, auth_plugin_name .. " plugin is not 
supported"
+                end
+            end
+        end
+    end
+
+    return true
+end
+
+function _M.rewrite(conf, ctx)
+    local auth_plugins = conf.auth_plugins
+    local status_code
+    for k, auth_plugin in pairs(auth_plugins) do
+        for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do
+            local auth = require("apisix.plugins." .. auth_plugin_name)
+            -- returns 401 HTTP status code if authentication failed, 
otherwise returns nothing.
+            local auth_code = auth.rewrite(auth_plugin_conf, ctx)
+            status_code = auth_code
+            if auth_code == nil then
+                core.log.debug(auth_plugin_name .. " succeed to authenticate 
the request")
+                goto authenticated
+            else
+                core.log.debug(auth_plugin_name .. " failed to authenticate 
the request, code: "
+                        .. auth_code)
+            end
+        end
+    end
+
+    :: authenticated ::
+    if status_code ~= nil then
+        return 401, { message = "Authorization Failed" }
+    end
+end
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 4e257f4d4..8717d1398 100755
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -457,6 +457,7 @@ plugins:                           # plugin list (sorted by 
priority)
   - uri-blocker                    # priority: 2900
   - request-validation             # priority: 2800
   - chaitin-waf                    # priority: 2700
+  - multi-auth                     # priority: 2600
   - openid-connect                 # priority: 2599
   - cas-auth                       # priority: 2597
   - authz-casbin                   # priority: 2560
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 20d44433f..3482517bd 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -111,7 +111,8 @@
             "plugins/authz-casbin",
             "plugins/ldap-auth",
             "plugins/opa",
-            "plugins/forward-auth"
+            "plugins/forward-auth",
+            "plugins/multi-auth"
           ]
         },
         {
diff --git a/docs/en/latest/getting-started/key-authentication.md 
b/docs/en/latest/getting-started/key-authentication.md
index 725bcd352..f6e8a8383 100644
--- a/docs/en/latest/getting-started/key-authentication.md
+++ b/docs/en/latest/getting-started/key-authentication.md
@@ -28,6 +28,7 @@ APISIX has a flexible plugin extension system and a number of 
existing plugins f
 - [LDAP](https://apisix.apache.org/docs/apisix/plugins/ldap-auth/)
 - [Open Policy Agent (OPA)](https://apisix.apache.org/docs/apisix/plugins/opa/)
 - [Forward 
Authentication](https://apisix.apache.org/docs/apisix/plugins/forward-auth/)
+- [Multiple 
Authentications](https://apisix.apache.org/docs/apisix/plugins/multi-auth/)
 
 In this tutorial, you will create a _consumer_ with _key authentication_, and 
learn how to enable and disable key authentication.
 
diff --git a/docs/en/latest/plugins/multi-auth.md 
b/docs/en/latest/plugins/multi-auth.md
new file mode 100644
index 000000000..703fac19a
--- /dev/null
+++ b/docs/en/latest/plugins/multi-auth.md
@@ -0,0 +1,144 @@
+---
+title: multi-auth
+keywords:
+  - Apache APISIX
+  - API Gateway
+  - Plugin
+  - Multi Auth
+  - multi-auth
+description: This document contains information about the Apache APISIX 
multi-auth Plugin.
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+## Description
+
+The `multi-auth` Plugin is used to add multiple authentication methods to a 
Route or a Service. It supports plugins of type 'auth'. You can combine 
different authentication methods using "or" relationship with `multi-auth` 
plugin. If you want to use multiple methods in an "and" relationship, apply 
specific authentication plugins directly to the route or service.
+
+## Attributes
+
+For Route:
+
+| Name         | Type  | Required | Default | Description                      
                                     |
+|--------------|-------|----------|---------|-----------------------------------------------------------------------|
+| auth_plugins | array | True     | -       | Add supporting auth plugins 
configuration. expects at least 2 plugins |
+
+## Enable Plugin
+
+To enable the Plugin, you have to create a Consumer object with multiple 
authentication configurations:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/consumers -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "foo",
+    "plugins": {
+        "basic-auth": {
+            "username": "foo",
+            "password": "bar"
+        },
+        "key-auth": {
+            "key": "auth-one"
+        }
+    }
+}'
+```
+
+You can also use the [APISIX Dashboard](/docs/dashboard/USER_GUIDE) to 
complete the operation through a web UI.
+
+Once you have created a Consumer object, you can then configure a Route or a 
Service to authenticate requests:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/hello",
+    "plugins": {
+        "multi-auth":{
+         "auth_plugins":[
+            {
+               "basic-auth":{ }
+            },
+            {
+               "key-auth":{
+                  "query":"apikey",
+                  "hide_credentials":true,
+                  "header":"apikey"
+               }
+            }
+         ]
+      }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
+
+## Example usage
+
+After you have configured the Plugin as mentioned above, you can make a 
request to the Route as shown below:
+
+request with basic-auth
+
+```shell
+curl -i -ufoo:bar http://127.0.0.1:9080/hello
+```
+
+request with key-auth
+
+```shell
+curl http://127.0.0.2:9080/hello -H 'apikey: auth-one' -i
+```
+
+```
+HTTP/1.1 200 OK
+...
+hello, world
+```
+
+If the request is not authorized, an error will be thrown:
+
+```shell
+HTTP/1.1 401 Unauthorized
+...
+{"message":"Authorization Failed"}
+```
+
+## Delete Plugin
+
+To remove the `multi-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.
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/hello",
+    "plugins": {},
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
diff --git a/docs/zh/latest/getting-started/key-authentication.md 
b/docs/zh/latest/getting-started/key-authentication.md
index ddcce9029..022f020ae 100644
--- a/docs/zh/latest/getting-started/key-authentication.md
+++ b/docs/zh/latest/getting-started/key-authentication.md
@@ -28,6 +28,7 @@ APISIX 拥有灵活的插件扩展系统,目前有很多可用于用户身份
 - [LDAP](https://apisix.apache.org/zh/docs/apisix/plugins/ldap-auth/)
 - [Open Policy Agent 
(OPA)](https://apisix.apache.org/zh/docs/apisix/plugins/opa/)
 - [Forward 
Authentication](https://apisix.apache.org/zh/docs/apisix/plugins/forward-auth/)
+- [Multiple 
Authentications](https://apisix.apache.org/docs/apisix/plugins/multi-auth/)
 
 本教程中,你将创建一个带有 _密钥验证_ 插件的 _消费者_,并学习如何启用和停用身份验证插件。
 
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index e7bcf09e9..e7b736fd9 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -75,6 +75,7 @@ csrf
 uri-blocker
 request-validation
 chaitin-waf
+multi-auth
 openid-connect
 cas-auth
 authz-casbin
@@ -311,7 +312,7 @@ 
qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"
         }
     }
 --- response_body eval
-qr/\[\{"name":"wolf-rbac","priority":2555\},\{"name":"ldap-auth","priority":2540\},\{"name":"hmac-auth","priority":2530\},\{"name":"basic-auth","priority":2520\},\{"name":"jwt-auth","priority":2510\},\{"name":"key-auth","priority":2500\}\]/
+qr/\[\{"name":"multi-auth","priority":2600\},\{"name":"wolf-rbac","priority":2555\},\{"name":"ldap-auth","priority":2540\},\{"name":"hmac-auth","priority":2530\},\{"name":"basic-auth","priority":2520\},\{"name":"jwt-auth","priority":2510\},\{"name":"key-auth","priority":2500\}\]/
 
 
 
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 5d645e8e6..f2bdbb2c9 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -53,6 +53,7 @@ loaded plugin and sort by priority: 3000 name: ip-restriction
 loaded plugin and sort by priority: 2990 name: referer-restriction
 loaded plugin and sort by priority: 2900 name: uri-blocker
 loaded plugin and sort by priority: 2800 name: request-validation
+loaded plugin and sort by priority: 2600 name: multi-auth
 loaded plugin and sort by priority: 2599 name: openid-connect
 loaded plugin and sort by priority: 2555 name: wolf-rbac
 loaded plugin and sort by priority: 2530 name: hmac-auth
diff --git a/t/plugin/multi-auth.t b/t/plugin/multi-auth.t
new file mode 100644
index 000000000..78ec19481
--- /dev/null
+++ b/t/plugin/multi-auth.t
@@ -0,0 +1,414 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+run_tests;
+
+__DATA__
+
+=== TEST 1: add consumer with basic-auth and key-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": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        },
+                        "key-auth": {
+                            "key": "auth-one"
+                        }
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: enable multi auth plugin using admin api
+--- 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": {
+                        "multi-auth": {
+                            "auth_plugins": [
+                                {
+                                    "basic-auth": {}
+                                },
+                                {
+                                    "key-auth": {
+                                        "query": "apikey",
+                                        "hide_credentials": true,
+                                        "header": "apikey"
+                                    }
+                                },
+                                {
+                                    "jwt-auth": {
+                                        "cookie": "jwt",
+                                        "query": "jwt",
+                                        "hide_credentials": true,
+                                        "header": "authorization"
+                                    }
+                                }
+                            ]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 3: verify, missing authorization
+--- request
+GET /hello
+--- error_code: 401
+--- response_body
+{"message":"Authorization Failed"}
+
+
+
+=== TEST 4: verify basic-auth
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOmJhcg==
+--- response_body
+hello world
+--- error_log
+find consumer foo
+
+
+
+=== TEST 5: verify key-auth
+--- request
+GET /hello
+--- more_headers
+apikey: auth-one
+--- response_body
+hello world
+
+
+
+=== TEST 6: verify, invalid basic credentials
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic YmFyOmJhcgo=
+--- error_code: 401
+--- response_body
+{"message":"Authorization Failed"}
+
+
+
+=== TEST 7: verify, invalid api key
+--- request
+GET /hello
+--- more_headers
+apikey: auth-two
+--- error_code: 401
+--- response_body
+{"message":"Authorization Failed"}
+
+
+
+=== TEST 8: enable multi auth plugin using admin api, without any auth_plugins 
configuration
+--- 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": {
+                        "multi-auth": { }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/\{"error_msg":"failed to check the configuration of plugin multi-auth err: 
property \\"auth_plugins\\" is required"\}/
+
+
+
+=== TEST 9: enable multi auth plugin using admin api, with auth_plugins 
configuration but with one authorization 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": {
+                        "multi-auth": {
+                            "auth_plugins": [
+                                {
+                                    "basic-auth": {}
+                                }
+                            ]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/\{"error_msg":"failed to check the configuration of plugin multi-auth err: 
property \\"auth_plugins\\" validation failed: expect array to have at least 2 
items"\}/
+
+
+
+=== TEST 10: create public API route (jwt-auth sign)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "public-api": {}
+                        },
+                        "uri": "/apisix/plugin/jwt/sign"
+                 }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 11: add consumer with username and jwt-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": "jack",
+                    "plugins": {
+                        "jwt-auth": {
+                            "key": "user-key",
+                            "secret": "my-secret-key"
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 12: sign / verify jwt-auth
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, err, sign = t('/apisix/plugin/jwt/sign?key=user-key',
+                ngx.HTTP_GET
+            )
+
+            if code > 200 then
+                ngx.status = code
+                ngx.say(err)
+                return
+            end
+
+            local code, _, res = t('/hello?jwt=' .. sign,
+                ngx.HTTP_GET
+            )
+
+            ngx.status = code
+            ngx.print(res)
+        }
+    }
+--- request
+GET /t
+--- response_body
+hello world
+
+
+
+=== TEST 13: verify multi-auth with plugin config will cause the conf_version 
change
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, err = t('/apisix/admin/plugin_configs/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "desc": "Multiple Authentication",
+                    "plugins": {
+                        "multi-auth": {
+                            "auth_plugins": [
+                                {
+                                    "basic-auth": {}
+                                },
+                                {
+                                    "key-auth": {
+                                        "query": "apikey",
+                                        "hide_credentials": true,
+                                        "header": "apikey"
+                                    }
+                                },
+                                {
+                                    "jwt-auth": {
+                                        "cookie": "jwt",
+                                        "query": "jwt",
+                                        "hide_credentials": true,
+                                        "header": "authorization"
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                  }]]
+            )
+            if code > 300 then
+                ngx.log(ngx.ERR, err)
+                return
+            end
+
+            local code, err = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/hello",
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "plugin_config_id": 1
+                }]]
+            )
+            if code > 300 then
+                ngx.log(ngx.ERR, err)
+                return
+            end
+            ngx.sleep(0.1)
+
+            local code, err, sign = t('/apisix/plugin/jwt/sign?key=user-key',
+                ngx.HTTP_GET
+            )
+
+            if code > 200 then
+                ngx.status = code
+                ngx.say(err)
+                return
+            end
+
+            local code, _, res = t('/hello?jwt=' .. sign,
+                ngx.HTTP_GET
+            )
+
+            ngx.status = code
+            ngx.print(res)
+        }
+    }
+--- request
+GET /t
+--- response_body
+hello world

Reply via email to