OPA
opa 插件支持与 Open Policy Agent (OPA) 集成,OPA 是一个统一的策略引擎和框架,有助于定义和强制执行授权策略。授权逻辑在 Rego 中定义并存储在 OPA 中。
配置后,OPA 引擎将评估对受保护路由的客户端 请求,根据定义的策略确定请求是否有权访问上游资源。
示例
以下示例展示了如何在不同场景下使用 opa 插件。
在继续之前,你应该拥有一个正在运行的 OPA 服务器,或者在 Docker 中启动一个新的:
docker run -d --name opa-server -p 8181:8181 openpolicyagent/opa:1.6.0 run --server --addr :8181 --log-level debug
run -s将 OPA 作为服务器启动。--log-level debug打印调试信息,以检查 APISIX 推送到 OPA 的数据。
要验证 OPA 服务器是否已安装且端口正确暴露,请运行:
curl http://127.0.0.1:8181 | grep Version
你应该看到类似以下的响应:
Version: 1.6.0
实现基本策略
以下示例在 OPA 中实现了一个基本的授权策略,仅允许 GET 请求。
创建一个仅允许 HTTP GET 请求的 OPA 策略:
curl "http://127.0.0.1:8181/v1/policies/getonly" -X PUT \
-H "Content-Type: text/plain" \
-d '
package getonly
default allow = false
allow if {
input.request.method == "GET"
}'
创建一个使用 opa 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"uri": "/anything",
"plugins": {
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "getonly"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 getonly。
要验证该策略,向路由发送一个 GET 请求 :
curl -i "http://127.0.0.1:9080/anything"
你应该收到 HTTP/1.1 200 OK 响应。
向路由发送另一个使用 PUT 的请求:
curl -i "http://127.0.0.1:9080/anything" -X PUT
你应该收到 HTTP/1.1 403 Forbidden 响应。
理解数据格式
以下示例有助于你理解 APISIX 推送 到 OPA 以支持授权逻辑编写的数据及其格式。该示例沿用上一个示例中的策略和路由。
假设你的 OPA 服务器已使用 --log-level debug 启动,并且你已完成上一个示例中的验证步骤,向示例路由发送了请求。
查看 OPA 服务器日志。你应该看到类似以下的条目:
{
"client_addr": "192.168.215.1:58467",
"level": "info",
"msg": "Received request.",
"req_body": "{\"input\":{\"type\":\"http\",\"var\":{\"server_port\":\"9080\",\"timestamp\":1752400020,\"server_addr\":\"192.168.107.3\",\"remote_port\":\"58544\",\"remote_addr\":\"192.168.107.1\"},\"request\":{\"host\":\"127.0.0.1\",\"path\":\"/anything\",\"headers\":{\"host\":\"127.0.0.1:9080\",\"accept\":\"*/*\",\"user-agent\":\"curl/8.6.0\"},\"query\":{},\"port\":9080,\"scheme\":\"http\",\"method\":\"PUT\"}}}",
"req_id": 12,
"req_method": "POST",
"req_params": {},
"req_path": "/v1/data/getonly",
"time": "2025-07-14T15:07:00Z"
}
其中 req_body 显示了 APISIX 推送的数据:
{
"input": {
"type": "http",
"var": {
"server_port": "9080",
"timestamp": 1752400020,
"server_addr": "192.168.107.3",
"remote_port": "58544",
"remote_addr": "192.168.107.1"
},
"request": {
"host": "127.0.0.1",
"path": "/anything",
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"query": {},
"port": 9080,
"scheme": "http",
"method": "PUT"
}
}
}
现在,更新之前创建的路由上的插件以包含路由信息:
curl "http://127.0.0.1:9180/apisix/admin/routes/opa-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"opa": {
"with_route": true
}
}
}'
向路由发送请求:
curl -i "http://127.0.0.1:9080/anything"
在 OPA 服务器日志中,你应该看到一个新的条目:
{
"client_addr": "192.168.215.1:43706",
"level": "info",
"msg": "Received request.",
"req_body": "{\"input\":{\"route\":{\"id\":\"opa-route\",\"uri\":\"/anything\",\"update_time\":1752395758,\"plugins\":{\"opa\":{\"keepalive_pool\":5,\"keepalive_timeout\":60000,\"host\":\"http://172.17.1.196:8181\",\"ssl_verify\":true,\"with_route\":true,\"with_service\":false,\"with_consumer\":false,\"timeout\":3000,\"keepalive\":true,\"policy\":\"getonly\"}},\"priority\":0,\"status\":1,\"create_time\":1752393063},\"type\":\"http\",\"var\":{\"server_port\":\"9080\",\"timestamp\":1752396233,\"server_addr\":\"192.168.107.3\",\"remote_port\":\"47838\",\"remote_addr\":\"192.168.107.1\"},\"request\":{\"host\":\"127.0.0.1\",\"path\":\"/anything\",\"headers\":{\"host\":\"127.0.0.1:9080\",\"accept\":\"*/*\",\"user-agent\":\"curl/8.6.0\"},\"query\":{},\"port\":9080,\"scheme\":\"http\",\"method\":\"GET\"}}}",
"req_id": 14,
"req_method": "POST",
"req_params": {},
"req_path": "/v1/data/getonly",
"time": "2025-07-13T08:43:53Z"
}
req_body 现在包含了路由信息:
{
"input": {
"route": {
"id": "opa-route",
"uri": "/anything",
"update_time": 1752395758,
"plugins": {
"opa": {
"keepalive_pool": 5,
"keepalive_timeout": 60000,
"host": "http://172.17.1.196:8181",
"ssl_verify": true,
"with_route": true,
"with_service": false,
"with_consumer": false,
"timeout": 3000,
"keepalive": true,
"policy": "getonly"
}
},
"priority": 0,
"status": 1,
"create_time": 1752393063
},
"type": "http",
"var": {
"server_port": "9080",
"timestamp": 1752396233,
"server_addr": "192.168.107.3",
"remote_port": "47838",
"remote_addr": "192.168.107.1"
},
"request": {
"host": "127.0.0.1",
"path": "/anything",
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"query": {},
"port": 9080,
"scheme": "http",
"method": "GET"
}
}
}
返回自定义响应
以下示例展示了如何在请求未获授权时返回自定义响应代码和消息。
创建一个仅允许 HTTP GET 请求并在未获授权时返回 302 和自定义消息的 OPA 策略:
curl "127.0.0.1:8181/v1/policies/customresp" -X PUT \
-H "Content-Type: text/plain" \
-d '
package customresp
default allow = false
allow if {
input.request.method == "GET"
}
reason := "The resource has temporarily moved. Please follow the new URL." if {
not allow
}
headers := {
"Location": "http://example.com/auth"
} if {
not allow
}
status_code := 302 if {
not allow
}
'
创建一个使用 opa 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"uri": "/anything",
"plugins": {
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "customresp"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❷ 将授权策略设置为 customresp。
向路由发送一个 GET 请求:
curl -i "http://127.0.0.1:9080/anything"
你应该收到 HTTP/1.1 200 OK 响应。
向路由发送一个 POST 请求:
curl -i "http://127.0.0.1:9080/anything" -X POST
你应该收到 HTTP/1.1 302 Moved Temporarily 响应:
HTTP/1.1 302 Moved Temporarily
...
Location: http://example.com/auth
The resource has temporarily moved. Please follow the new URL.
实现 RBAC
以下示例展示了如何使用 jwt-auth 和 opa 插件实现身份验证和 RBAC。你将实现以下 RBAC 逻辑:
user角色只能读取上游资源。admin角色可以读取和写入上游资源。
为两个示例消费者创建 RBAC 的 OPA 策略,其中 john 拥有 user 角色,jane 拥有 admin 角色:
curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
-H "Content-Type: text/plain" \
-d '
package rbac
# Assign roles to users
user_roles := {
"john": ["user"],
"jane": ["admin"]
}
# Map permissions to HTTP methods
permission_methods := {
"read": "GET",
"write": "POST"
}
# Assign role permissions
role_permissions := {
"user": ["read"],
"admin": ["read", "write"]
}
# Get JWT authorization token
bearer_token := t if {
t := input.request.headers.authorization
}
# Decode the token to get role and permission
token := {"payload": payload} if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
# Normalize permission to a list
normalized_permissions := ps if {
ps := token.payload.permission
not is_string(ps)
}
normalized_permissions := [ps] if {
ps := token.payload.permission
is_string(ps)
}
# Implement RBAC logic
default allow = false
allow if {
# Look up the list of roles for the user
roles := user_roles[input.consumer.username]
# For each role in that list
r := roles[_]
# Look up the permissions list for the role
permissions := role_permissions[r]
# For each permission
p := permissions[_]
# Check if the permission matches the request method
permission_methods[p] == input.request.method
# Check if the normalized permissions include the permission
p in normalized_permissions
}
'
在 APISIX 中创建两个消费者 john 和 jane:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT -d '
{
"username": "john"
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT -d '
{
"username": "jane"
}'
使用默认算法 HS256 为消费者配置 jwt-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "john-key",
"secret": "john-hs256-secret-that-is-very-long"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jane-key",
"secret": "jane-hs256-secret-that-is-very-long"
}
}
}'
创建一个路由并配置 jwt-auth 和 opa 插件,如下所示:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "opa-route",
"methods": ["GET", "POST"],
"uris": ["/get","/post"],
"plugins": {
"jwt-auth": {},
"opa": {
"host": "http://192.168.2.104:8181",
"policy": "rbac",
"with_consumer": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
❶ 在路由上启用 jwt-auth 插件。
❷ 配置 OPA 服务器地址。请替换为你的 IP 地址。
❸ 将授权策略设置为 rbac。
❹ 将 with_consumer 设置为 true 以发送消费者信息。
验证 john
要为 john 颁发 JWT,你可以使用 JWT.io 的 JWT 编码器或其他工具。如果你使用 JWT.io 的 JWT 编码器,请执行以下操作:
- 将算法填写为
HS256。 - 将 Valid secret 部分中的密钥更新为
john-hs256-secret-that-is-very-long。 - 更新 payload,角色为
user,权限为read,消费者密钥为john-key;以及exp或nbf为 UNIX 时间戳。
如果你使用的是 API7 企 业版,则 exp 或 nbf 的要求不是强制性的。你可以选择包含这些声明,并使用 claims_to_verify 参数配置要验证的声明。
你的 payload 应该类似于以下内容:
{
"role": "user",
"permission": "read",
"key": "john-key",
"nbf": 1729132271
}
复制生成的 JWT 并保存到变量:
export john_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsInBlcm1pc3Npb24iOiJyZWFkIiwia2V5Ijoiam9obi1rZXkiLCJuYmYiOjE3MjkxMzIyNzF9.rAHMTQfnnGFnKYc3am_lpE9pZ9E8EaOT_NBQ5Ss8pk4
使用 john 的 JWT 向路由发送 GET 请求:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${john_jwt_token}"
你应该收到 HTTP/1.1 200 OK 响应。
使用相同的 JWT 向路由发送 POST 请求:
curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: ${john_jwt_token}"
你应该收到 HTTP/1.1 403 Forbidden 响应。
验证 jane
同样,要为 jane 颁发 JWT,你可以使用 JWT.io 的 JWT 编码器或其他工具。如果你使用 JWT.io 的 JWT 编码器,请执行以下操作:
- 将算法填写为
HS256。 - 将 Valid secret 部分中的密钥更新为
jane-hs256-secret-that-is-very-long。 - 更新 payload,角色为
admin,权限为["read","write"],消费者密钥为jane-key;以及exp或nbf为 UNIX 时间戳。
如果你使用的是 API7 企业版,则 exp 或 nbf 的要求不是强制性的。你可以选择包含这些声明,并使用 claims_to_verify 参数配置要验证的声明。
你的 payload 应该类似于以下内容:
{
"role": "admin",
"permission": ["read","write"],
"key": "jane-key",
"nbf": 1729132271
}
复制生成的 JWT 并保存到变量:
export jane_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJwZXJtaXNzaW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJrZXkiOiJqYW5lLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.meZ-AaGHUPwN_GvVOE3IkKuAJ1wqlCguaXf3gm3Ww8s
使用 jane 的 JWT 向路由发送 GET 请求:
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jane_jwt_token}"
你应该收到 HTTP/1.1 200 OK 响应。
使用相同的 JWT 向路由发送 POST 请求:
curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: ${jane_jwt_token}"
你应该也收到 HTTP/1.1 200 OK 响应。
如果你设置了 --log-level debug,要检查授权决策是否来自 OPA,你应该在 OPA 服务器中观察到以下日志:
{
"result":{
"allow": true,
"bearer_token": "eyJ...",
...
}
}