Apache APISIX wolf-rbac 插件实战:基于 wolf 的 RBAC 身份认证与权限控制
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
导读
wolf-rbac是 Apache APISIX 中一个基于 Role-Based Access Control(RBAC,基于角色的访问控制) 为主体,结合 插件源码 与 单元测试 展开,带你完整掌握:插件的属性与接口、Consumer 与 Route 的配置方式、如何借助public-api插件暴露登录/改密/用户信息接口,以及 rbac_token 的多种携带方式与底层鉴权原理。
描述:为网关接入 wolf RBAC 鉴权
wolf-rbac插件为 APISIX 的 Route 或 Service 提供接入 wolf 权限系统的能力。它是一个认证(auth)类型插件,从源码看其优先级为2555,类型为'auth'(见 apisix/plugins/wolf-rbac.lua),意味着它会在路由匹配之后、其他业务插件执行之前完成身份与权限校验。
该插件必须与 Consumer 配合使用:插件通过 Consumer 上绑定的appid关联到具体的应用,再以该应用的身份向 wolf-server 发起权限校验。从 Consumer 术语文档 可以看到,wolf-rbac与basic-auth、hmac-auth、jwt-auth、key-auth、ldap-auth并列,是 APISIX 支持与 Consumer 配置搭配的认证插件之一。
其整体工作流程可以概括为:
- 用户在 wolf 系统中维护应用(appid)、用户、角色、权限、资源等数据;
- APISIX 在 Consumer 上绑定
wolf-rbac插件并声明appid; - 客户端携带
rbac_token请求受保护 Route; - 插件解析 token,向 wolf-server 的
/wolf/rbac/access_check接口查询"该用户对此 URL 的该 HTTP 方法是否有权限"; - 校验通过则放行并注入用户信息响应头,失败则返回 401/403 等状态码。
属性说明
wolf-rbac插件的配置属性如下表所示,这些属性与源码中的 schema 定义 一一对应:
| 名称 | 类型 | 必选项 | 默认值 | 描述 |
|---|---|---|---|---|
server | string | 否 | "http://127.0.0.1:12180" | wolf-server的服务地址。 |
appid | string | 否 | "unset" | 在wolf-console中已经添加的应用 ID。该字段支持使用 APISIX Secret 资源,将值保存在 Secret Manager 中。 |
header_prefix | string | 否 | "X-" | 自定义 HTTP 头的前缀。鉴权成功后,插件会在请求头(传给后端)与响应头(传给前端)中额外添加 3 个 header:X-UserId、X-Username、X-Nickname。 |
参数细节与源码印证
server:即 wolf-server 的访问地址,默认指向本机12180端口。从源码看,check_schema 会通过core.utils.check_https检查该地址是否为https开头,若是 HTTPS 地址则要求 APISIX 已加载相应的 SSL 证书,否则返回 schema 校验错误。appid:对应 wolf 控制台中注册的应用 ID,是关联 wolf 侧权限数据的核心字段。它的默认值是字符串"unset",在单元测试 TEST 1: sanity 中可以看到,当配置为空对象时,schema 校验会填充出{"appid":"unset","header_prefix":"X-","server":"http://127.0.0.1:12180"}的完整默认配置。appid支持 Secret 引用:appid可写成$secret://vault/test1/wolf_rbac_unit_test/appid这样的引用形式,将敏感值托管到 Vault 等 Secret Manager 中。这一点在 测试用例 TEST 31-35 中有完整的验证:先创建 vault secret 资源,再在 Consumer 的wolf-rbac配置中使用$secret://引用,登录测试依然通过。header_prefix:控制注入头的前缀。例如设置为X-时注入X-UserId、X-Username、X-Nickname;若设置为空字符串,则注入不带前缀的UserId、Username、Nickname。
插件接口
启用该插件后,它会在 APISIX 内部注册以下 3 个接口(对应源码 _M.api() 的注册表):
| 接口 | 方法 | 用途 |
|---|---|---|
/apisix/plugin/wolf-rbac/login | POST | 用户名密码登录,换取rbac_token |
/apisix/plugin/wolf-rbac/change_pwd | PUT | 修改当前用户密码 |
/apisix/plugin/wolf-rbac/user_info | GET | 获取当前用户信息 |
:::note 注意
以上接口默认不对外暴露,需要通过 public-api 插件创建路由后才能从外部访问。这与public-api插件的设计一致:自定义插件注册的公共 API 端点默认隐藏,需手动配置路由启用。
:::
从源码实现看,这三个接口的 handler 都遵循同样的模式:
wolf_rbac_login(源码):解析请求体中的appid、username、password、authType等参数,从 Consumer 配置中取出server地址,调用 wolf-server 的/wolf/rbac/login.rest接口完成认证,然后将 wolf 返回的 token 拼装成V1#appid#wolf_token格式的rbac_token返回给客户端。wolf_rbac_change_pwd(源码):先从当前请求中解析rbac_token(缺 token 返回 401),再携带x-rbac-token请求头调用 wolf-server 的/wolf/rbac/change_pwd接口。wolf_rbac_user_info(源码):同样先校验 token,再调用 wolf-server 的/wolf/rbac/user_info接口返回用户详情。
前提条件:安装并初始化 wolf
在使用插件之前,你必须要:
- 安装并启动 wolf:参考 wolf 官方提供的 Docker 快速启动方式完成部署。
- 初始化权限数据:在 wolf-console 控制台中添加
application(应用)、admin(管理员)、regular user(普通用户)、permission(权限)、resource(资源)等数据,并将用户授权到对应应用。
注意:wolf-console 中维护的数据是鉴权的"事实来源"。APISIX 只负责把请求转发给 wolf-server 进行校验,插件本身不存储用户与权限信息。
启用插件
第一步:创建 Consumer 并绑定插件
首先创建一个 Consumer,并在其plugins中配置wolf-rbac:
:::note
可以这样从 conf/config.yaml 中获取admin_key并存入环境变量(admin_key定义于deployment.admin.admin_key配置项):
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g'):::
curl http://127.0.0.1:9180/apisix/admin/consumers \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "username":"wolf_rbac", "plugins":{ "wolf-rbac":{ "server":"http://127.0.0.1:12180", "appid":"restful" } }, "desc":"wolf-rbac" }':::note
示例中填写的appid(这里是restful),必须是已经在 wolf 控制台中存在的应用 ID,否则后续登录与鉴权都会失败。
:::
为什么appid要放在 Consumer 上?从源码的鉴权流程(rewrite 函数)可以看出,插件在收到请求后会先解析 token 中的appid,然后通过consumer.consumers_kv(plugin_name, consumer_conf, "appid")按appid索引找到对应的 Consumer 配置,进而取得该应用的server地址。也就是说:一个 appid 对应一个 Consumer,token 中的 appid 决定了使用哪个 Consumer 的 wolf-server 配置。如果 token 里的 appid 找不到对应的 Consumer,插件会直接返回 401Invalid appid in rbac token(对应测试 TEST 15)。
第二步:为 Route 或 Service 添加插件
然后,将wolf-rbac插件挂载到需要保护的 Route 上。下面的示例让 Route 1 匹配所有 GET 请求并启用插件(配置项留空即可,全部使用默认值):
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "methods": ["GET"], "uri": "/*", "plugins": { "wolf-rbac": {} }, "upstream": { "type": "roundrobin", "nodes": { "www.baidu.com:80": 1 } } }'同样地,插件也可以挂载到 Service 上,这样该 Service 下的所有 Route 都会继承鉴权配置。此外,你还可以通过 APISIX Dashboard 的 Web 界面完成上述操作。
第三步:暴露插件的三个接口(public-api)
由于/apisix/plugin/wolf-rbac/login等接口默认不对外暴露,需要创建路由并启用 public-api 插件来将它们开放出来:
curl http://127.0.0.1:9180/apisix/admin/routes/wal \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/apisix/plugin/wolf-rbac/login", "plugins": { "public-api": {} } }'同样,你需要参考上述命令为change_pwd和user_info两个 API 分别配置路由(在单元测试 TEST 3: setup public API route 中,正是依次为login、user_info、change_pwd创建了三条 public-api 路由)。
测试插件
登录并获取 rbac_token
在配置好 public-api 路由后,就可以通过网关(默认9080端口)调用登录接口了。JSON 格式登录:
curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \ -H "Content-Type: application/json" \ -d '{"appid": "restful", "username":"test", "password":"user-password", "authType":1}'返回示例(200 OK,响应体包含rbac_token与user_info):
HTTP/1.1 200 OK Date: Wed, 24 Jul 2019 10:33:31 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Server: APISIX web server {"rbac_token":"V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts","user_info":{"nickname":"test","username":"test","id":"749"}}x-www-form-urlencoded 格式登录:
curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/login -i \ -H "Content-Type: application/x-www-form-urlencoded" \ -d 'appid=restful&username=test&password=user-password':::note
- 上述示例中,
appid、username和password必须为 wolf 系统中真实存在的; authType为认证类型:1为密码认证(默认),2为 LDAP 认证。wolf从 0.5.0 版本开始支持 LDAP 认证。
:::
rbac_token 的格式:从源码 create_rbac_token 可以看到,token 由三段以#分隔的字段组成:版本号(V1)#appid#wolf_token。其中第三段是 wolf 返回的原始 token(实际为 JWT,包含id、username、appid、iat、exp等声明)。解析时(parse_rbac_token)要求恰好拆成 3 段且版本号必须是V1,否则返回invalid rbac token: version。
登录失败场景:单元测试 TEST 6-11 覆盖了登录接口的各种异常路径:缺少appid返回 400appid is missing、appid 不存在返回 400appid not found、用户名/密码缺失或错误、用户不存在时,网关会转发 wolf-server 返回的错误码(如ERR_USERNAME_MISSING、ERR_PASSWORD_ERROR、ERR_USER_NOT_FOUND)并给出request to wolf-server failed!的响应。
使用受保护的 Route
现在开始测试 Route 的鉴权效果。以下场景均以示例中的rbac_token为准。
1. 缺少 token
curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" -iHTTP/1.1 401 Unauthorized ... {"message":"Missing rbac token in request"}2. token 放到请求头Authorization中
curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \ -H 'Authorization: V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts' -iHTTP/1.1 200 OK <!DOCTYPE html>3. token 放到请求头x-rbac-token中
curl http://127.0.0.1:9080/ -H"Host: www.baidu.com" \ -H 'x-rbac-token: V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts' -iHTTP/1.1 200 OK <!DOCTYPE html>4. token 放到请求参数中
curl 'http://127.0.0.1:9080?rbac_token=V1%23restful%23eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts' -H"Host: www.baidu.com" -iHTTP/1.1 200 OK <!DOCTYPE html>5. token 放到cookie中
curl http://127.0.0.1:9080 -H"Host: www.baidu.com" \ --cookie x-rbac-token=V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts -iHTTP/1.1 200 OK <!DOCTYPE html>token 的查找优先级:以上 4 种携带方式对应源码 fetch_rbac_token 的取值顺序——依次检查 URL 参数rbac_token(注意会先做ngx.unescape_uri解码)、请求头Authorization、请求头x-rbac-token、cookie_x-rbac-token。全部取不到时返回 nil,进而触发 401。单元测试 TEST 17-20 逐一验证了这 4 种方式,且都断言了响应头中注入了X-UserId: 100、X-Username: admin、X-Nickname: administrator。
获取用户信息
通过user_info接口,可以拿到当前 token 对应用户在 wolf 中的完整资料:
curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/user_info \ --cookie x-rbac-token=V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts -iHTTP/1.1 200 OK { "user_info":{ "nickname":"test", "lastLogin":1582816780, "id":749, "username":"test", "appIDs":["restful"], "manager":"none", "permissions":{"USER_LIST":true}, "profile":null, "roles":{}, "createTime":1578820506, "email":"" } }更改用户密码
通过change_pwd接口修改当前用户的密码(需要携带旧密码与新密码,同时以 cookie 方式提供 token):
curl http://127.0.0.1:9080/apisix/plugin/wolf-rbac/change_pwd \ -H "Content-Type: application/json" \ --cookie x-rbac-token=V1#restful#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzQ5LCJ1c2VybmFtZSI6InRlc3QiLCJtYW5hZ2VyIjoiIiwiYXBwaWQiOiJyZXN0ZnVsIiwiaWF0IjoxNTc5NDQ5ODQxLCJleHAiOjE1ODAwNTQ2NDF9.n2-830zbhrEh6OAxn4K_yYtg5pqfmjpZAjoQXgtcuts -i \ -X PUT -d '{"oldPassword": "old password", "newPassword": "new password"}'HTTP/1.1 200 OK {"message":"success to change password"}补充说明:从源码看,change_pwd与user_info接口都要求请求中携带有效 token(get_wolf_token),否则返回 401Missing rbac token in request或invalid rbac token: parse failed(对应测试 TEST 21-22)。同时请求体既支持 JSON 也支持 x-www-form-urlencoded(见 get_args,后者借助core.request.get_post_args解析),测试 TEST 27-28 验证了 form 方式以及超过 100 个参数场景下的可用性。
底层鉴权流程与故障处理(源码解析)
理解插件在rewrite阶段的完整处理链路,有助于在生产环境中排查问题。整个流程见 rewrite 函数:
- 提取上下文:获取当前请求的
uri、HTTP 方法(request_method)、客户端 IP(优先取X-Real-IP,否则通过core.request.get_ip获取); - 取 token:按上文优先级从参数/请求头/cookie 中提取
rbac_token,缺失则返回 401Missing rbac token in request; - 解析 token:校验格式与版本,失败返回 401
invalid rbac token: parse failed; - 匹配 Consumer:用 token 中的
appid反查 Consumer 配置,失败返回 401Invalid appid in rbac token; - 调用 wolf-server 校验:向
{server}/wolf/rbac/access_check?appID=...&resName=<uri>&action=<HTTP方法>&clientIP=...发起 GET 请求,并在请求头携带x-rbac-token。从 check_url_permission 可以看到,该调用带 10 秒超时,且对5xx响应最多重试 3 次(间隔 0.1 秒),以容忍 wolf-server 的瞬时故障; - 注入用户信息头:校验成功后,将
UserId、Username、Nickname按header_prefix拼装,同时写入响应头(core.response.set_header,用于传给前端)和请求头(core.request.set_header,用于传给后端)。其中Nickname会经过ngx.escape_uri编码,若用户未设置昵称则回退为username; - 处理失败响应:
res.status非 200 时,直接返回 wolf-server 给出的状态码与错误信息。典型场景包括:- 403:用户无权限,如
ERR_ACCESS_DENIED(测试 TEST 16); - 401:token 过期或无效,如
ERR_TOKEN_INVALID(测试 TEST 30); - 500:wolf-server 内部错误或网络不可达,如
request to wolf-server failed, status:500(测试 TEST 29)。
- 403:用户无权限,如
另外值得注意的是,wolf-server 的access_check会基于请求的 URI 与 HTTP 方法判断权限,因此同一个 Route 上不同方法的请求可能得到不同的鉴权结果——这正是 RBAC 中"资源 + 动作"模型的体现,resName即请求的uri,action即 HTTP 方法(如GET、POST)。
删除插件
当你需要禁用wolf-rbac插件时,只需通过 Admin API 将 Route(或 Service、Consumer)配置中的plugins清空,APISIX 会自动重新加载相关配置,无需重启服务:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "methods": ["GET"], "uri": "/*", "plugins": { }, "upstream": { "type": "roundrobin", "nodes": { "www.baidu.com:80": 1 } } }'注意:删除 Route 上的插件后,仅该 Route 不再鉴权。若之前在 Consumer 上也配置了
wolf-rbac,可根据需要一并删除;同时由public-api暴露的login、user_info、change_pwd三个接口路由,在不再需要时也应一并清理,避免鉴权接口继续对外开放。
小结
wolf-rbac插件将"身份认证 + 细粒度权限控制"从业务代码中剥离出来,交给网关统一处理:认证由 wolf 完成,APISIX 负责 token 解析、Consumer 匹配与权限查询,并通过注入的X-UserId/X-Username/X-Nickname头把用户身份透传给上游与前端。结合 Consumer、public-api 与 Secret 能力,它可以完整覆盖"登录换 token、携带 token 访问受保护资源、查询用户信息、修改密码"这一整套基于角色的访问控制场景。若需深入理解实现细节,可继续阅读 插件源码 与 单元测试。
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考