跳到主要内容

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-authopa 插件实现身份验证和 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 中创建两个消费者 johnjane

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-authopa 插件,如下所示:

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;以及 expnbf 为 UNIX 时间戳。
备注

如果你使用的是 API7 企业版,则 expnbf 的要求不是强制性的。你可以选择包含这些声明,并使用 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;以及 expnbf 为 UNIX 时间戳。
备注

如果你使用的是 API7 企业版,则 expnbf 的要求不是强制性的。你可以选择包含这些声明,并使用 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...",
...
}
}