微服务认证与授权:07 — Kong(网关 / PEP)

📅 2026/7/7 17:49:18 👁️ 阅读次数 📝 编程学习
微服务认证与授权:07 — Kong(网关 / PEP)

07 — Kong(网关 / PEP)

📦 GitHub: https://github.com/geekchow/micro-service-auth

Kong 是 API 网关,也是策略执行点(PEP):它拦截每个请求、用Keycloak校验令牌、向OPA请求授权决策,并把被允许的请求转发给banking-api-service

什么是 Kong

在本 PoC 所用的 IdP / PEP / PDP 分工中:

  • KeycloakIdP—— 它签发令牌。
  • KongPEP—— 它在边缘执行访问控制。
  • OPAPDP—— 它判定某个请求是否被允许。
  • banking-api-service资源服务器—— 它拥有受保护的银行数据。

Kong 在本项目中的职责:

  1. 接收传入的 API 请求。
  2. 拒绝缺失或格式错误的Authorization头的请求。
  3. 调用Keycloak内省,确认令牌当前处于 active。
  4. 从令牌载荷中解码 JWT 声明。
  5. 用请求路径、方法与声明构建一个 input 对象。
  6. 调用OPA获取授权决策。
  7. OPA拒绝则返回403,若OPA允许则转发给banking-api-service

Kong 不是身份提供方、不是策略引擎、也不是银行业务服务。

两个与 Kong 相关的配置文件

本仓库中有两个文件控制 Kong:

文件用途
docker-compose.yml容器串联 —— 镜像、端口、环境变量、卷、依赖
infra/kong/kong.ymlKong 运行时行为 —— service、route、plugin、plugin 配置值

它们之间的关系

docker-compose.yml

Kong container

infra/kong/kong.yml

infra/kong/plugins/opa-authz

banking-api-service

Keycloak

OPA

  • docker-compose.yml创建 Kong 容器并把配置挂载进去。
  • kong.yml告诉 Kong 要暴露什么路由、运行什么插件。
  • schema.lua定义哪些插件配置字段是合法的。
  • handler.lua包含每个请求都会运行的执行逻辑。

docker-compose.yml中的 Kong

相关的 Kong service 块:

kong:image:kong:3.7environment:KONG_DATABASE:offKONG_DECLARATIVE_CONFIG:/etc/kong/kong.ymlKONG_PLUGINS:bundled,opa-authzKONG_PROXY_ACCESS_LOG:/dev/stdoutKONG_ADMIN_ACCESS_LOG:/dev/stdoutKONG_PROXY_ERROR_LOG:/dev/stderrKONG_ADMIN_ERROR_LOG:/dev/stderrKONG_ADMIN_LISTEN:0.0.0.0:8001ports:-"8000:8000"-"8001:8001"depends_on:banking-api-service:condition:service_startedrestart:trueopa:condition:service_startedvolumes:-./infra/kong/kong.yml:/etc/kong/kong.yml:ro-./infra/kong/plugins/opa-authz:/usr/local/share/lua/5.1/kong/plugins/opa-authz:ro

关键环境变量:

  • KONG_DATABASE=off—— Kong 以无数据库(DB-less)模式运行。所有配置来自文件,而非数据库。
  • KONG_DECLARATIVE_CONFIG=/etc/kong/kong.yml—— 保存 Kong 声明式路由配置的文件。
  • KONG_PLUGINS=bundled,opa-authz—— 启用 Kong 内置插件以及自定义的opa-authz插件。
  • KONG_ADMIN_LISTEN=0.0.0.0:8001—— 在容器内暴露 Kong admin API。

关键卷:

  • kong.yml以只读方式挂载到容器内的/etc/kong/kong.yml
  • 自定义插件目录以只读方式挂载到/usr/local/share/lua/5.1/kong/plugins/opa-authz—— 这是 Kong 扫描的标准 Lua 插件路径。

depends_on意味着 Kong 仅在banking-api-serviceopa启动之后才启动。若banking-api-service重启,Kong 也会重启(restart: true)。

infra/kong/kong.yml中的 Kong

该文件使用 Kong 的声明式格式(_format_version: "3.0"),并定义了一个 service,内联一个 route 与一个 plugin:

_format_version:"3.0"services:-name:banking-apiurl:http://banking-api-service:8080routes:-name:banking-api-routepaths:-/api/accountsstrip_path:falseplugins:-name:opa-authzconfig:opa_url:http://opa:8181/v1/data/banking_authz/allowintrospection_url:http://keycloak:8080/realms/banking-poc/protocol/openid-connect/token/introspectintrospection_client_id:kong-introspectionintrospection_client_secret:kong-introspection-secrettimeout_ms:2000

Service

  • name: banking-api—— Kong 对上游的内部名称。
  • url: http://banking-api-service:8080—— Kong 把被允许的请求转发到这里。

Route

  • paths: [/api/accounts]—— 以/api/accounts开头的请求匹配此路由。
  • strip_path: false—— 路径按原样转发。

因此一个GET /api/accounts/A-1001的请求(来自aliceops-admin)会按GET /api/accounts/A-1001转发到上游。

插件挂载与配置值

opa-authz插件在每个匹配该路由的请求上运行。它的配置提供了插件在运行时使用的实际 URL 与凭据:

  • opa_url—— Kong 把授权 input POST 过去的 OPA 端点。
  • introspection_url—— Kong 调用来检查令牌活跃性的Keycloak端点。
  • introspection_client_id—— 在Keycloak中注册的kong-introspectionclient。
  • introspection_client_secret—— 该 client 配对的密钥。
  • timeout_ms: 2000—— Kong 在中止前等待KeycloakOPA的时长。

opa-authz插件

自定义插件位于infra/kong/plugins/opa-authz/,由两个文件组成:

schema.lua—— 配置定义

schema.lua声明了合法插件配置的形态。Kong 在启动时会用此 schema 校验任何opa-authz插件块:

return{name="opa-authz",fields={{config={type="record",fields={{opa_url={type="string",required=true}},{introspection_url={type="string",required=true}},{introspection_client_id={type="string",required=true}},{introspection_client_secret={type="string",required=true}},{timeout_ms={type="number",default=2000}},},},},},}
  • 四个 URL/凭据字段都是必填字符串 —— 任一缺失,Kong 都会拒绝启动。
  • timeout_ms是可选的,默认2000
  • schema.lua定义什么是被允许的;kong.yml提供实际值;handler.lua在运行时使用它们。

handler.lua—— 执行逻辑

handler.lua在 Kong 的access阶段(优先级900)对每个匹配的请求运行。逻辑按以下顺序执行:

第 1 步 —— 读取 bearer 令牌

localauth_header=kong.request.get_header("authorization")ifnotauth_headerthenreturnkong.response.exit(401,{message="missing bearer token"})endlocaltoken=auth_header:match("[Bb]earer%s+(.+)")ifnottokenthenreturnkong.response.exit(401,{message="invalid bearer token"})end

若不存在Authorization头、或其中不含 bearer 令牌,则返回401

第 2 步 —— 用Keycloak内省令牌

localintrospection,introspection_err=introspect_token(conf,token)ifnotintrospectionthenreturnkong.response.exit(503,{message="introspection unavailable",detail=introspection_err})endifintrospection.active~=truethenreturnkong.response.exit(401,{message="inactive token"})end

Kong 用introspection_client_idintrospection_client_secret以 HTTP Basic 认证,把令牌 POST 到Keycloak的内省端点。若Keycloak不可达,Kong 返回503。若令牌不是 active,Kong 返回401

关于 Kong 为何使用内省而非基于 JWKS 的本地校验,见下文「为什么 Kong 使用内省而非 JWKS」。关于内省机制上如何工作,见 11 — JWT 签名、校验与内省。

第 3 步 —— 解码 JWT 声明

localclaims=decode_claims(token)ifnotclaimsthenreturnkong.response.exit(401,{message="unreadable jwt payload"})end

Kong 对 JWT 载荷段做 base64 解码并 JSON 解析以读取声明。这是一次本地解码 —— 没有额外的网络调用。

第 4 步 —— 确定有效角色

localfunctioneffective_role(claims)localrealm_access=claimsandclaims.realm_accesslocalroles=realm_accessandrealm_access.rolesor{}for_,roleinipairs(roles)doifrole=="ops-admin"thenreturn"ops-admin"endendfor_,roleinipairs(roles)doifrole=="customer"thenreturn"customer"endendreturnnilend

ops-admin优先于customer。若两个角色都不存在,effective_role返回nil

第 5 步 —— 构建 OPA input 并调用OPA

localaccount_id=kong.request.get_path():match("/api/accounts/([^/]+)")localrequest_body=cjson.encode({input={method=kong.request.get_method(),path=kong.request.get_path(),account_id=account_id,customer_id=claim_value(claims.customer_id),account_ids=claim_values(claims.account_ids),role=effective_role(claims),username=claims.preferred_username,},})

对于像alice调用GET /api/accounts/A-1001这样的请求,Kong 从路径中提取account_id = "A-1001",并把它与其他字段打包成一个 JSON input 文档。

第 6 步 —— 执行OPA决策

ifdecision.result~=truethenreturnkong.response.exit(403,{message="forbidden"})end

OPA返回{"result": true}{"result": false}。Kong 检查decision.result—— 任何非true(包括false或字段缺失)都会导致403。若OPA不可达或返回非 200 状态,Kong 返回503

Kong 的请求流程

banking-api-serviceOPA (PDP)Keycloak (IdP)Kong (PEP)Client (alice / ops-admin)banking-api-serviceOPA (PDP)Keycloak (IdP)Kong (PEP)Client (alice / ops-admin)alt[deny][allow]alt[inactive][active]GET /api/accounts/A-1001 + Bearer tokenMatch /api/accounts routePOST introspect token{ "active": true } or { "active": false }401 inactive tokenDecode JWT claimsPOST { input: { method, path, account_id, customer_id, account_ids, role, username } }{ "result": true } or { "result": false }403 forbiddenForward GET /api/accounts/A-1001banking responsebanking response

与其他组件的互操作

Kong 与banking-api-service

Kong 只转发那些同时通过了Keycloak内省与OPA授权的请求。banking-api-service不直接对外暴露 —— 来自aliceops-admin的所有流量都经由 Kong 到达。

banking-api-service也会用 JWKS 自行校验 JWT。这是纵深防御:银行服务不会因为 Kong 已经检查过就完全信任。

Kong 与identity-bootstrap-service

Kong 并不位于identity-bootstrap-service之前。bootstrap 是一个内部演示工具,位于同一 Compose 网络上,但 Kong 不向它路由任何流量。

Kong 与Keycloak

Kong 使用Keycloak做令牌内省,而不是把它当作自身管理访问的身份提供方。

为什么 Kong 使用内省而非 JWKS

Kong 是边缘 PEP。在它用令牌声明构建 OPA input 之前,它需要从Keycloak得到一个实时确认:该令牌此刻仍然处于 active。

一个令牌可能:

  • 仍然能被正确解码为 JWT,
  • 仍然带有有效的密码学签名,
  • 仍然处在其exp过期窗口内,

Keycloak可能因为登出、会话过期或吊销,已经认为它 inactive。

基于 JWKS 的本地校验能确认签名、签发者、受众与过期 —— 但它无法反映Keycloak当前的会话状态。

内省让 Kong 在决定调用 OPA 之前,直接从Keycloak得到「真相之源」的答案。

因此设计是:

  • Kong 内省—— 在 OPA 之前、在边缘做实时令牌活跃性检查。
  • banking-api-serviceJWKS 校验—— 在资源服务器内部做快速的本地密码学校验。

关于内省如何工作的完整机制(HTTP 交互、Keycloak 的响应、active的含义),见 11 — JWT 签名、校验与内省。

Kong 与OPA

Kong 仅在令牌被确认 active 之后才调用OPAOPA接收到:

字段来源
method请求的 HTTP 方法
path请求路径
account_id/api/accounts/{account_id}提取
customer_idJWT 声明
account_idsJWT 声明(列表)
role推导得到:ops-admin|customer|nil
usernameJWT 的preferred_username声明

OPA返回{"result": true}{"result": false}。Kong 据此执行。

把策略放在OPA里,意味着授权规则可以在不修改 Kong 或银行服务的情况下变更。

实用检查命令

从 Compose 网络内部发起请求时,使用辅助容器:

dockercomposeexeccurlsh

检查Keycloak的 OIDC discovery:

dockercomposeexeccurlcurlhttp://keycloak:8080/realms/banking-poc/.well-known/openid-configuration

检查OPA的策略端点:

dockercomposeexeccurlcurlhttp://opa:8181/v1/data/banking_authz/allow

检查banking-api-service健康状态:

dockercomposeexeccurlcurlhttp://banking-api-service:8080/actuator/health

从宿主机检查 Kong:

# Admin API — shows loaded routes, services, pluginscurlhttp://localhost:8001# Proxy — test the protected route (expect 401 without a token)curl-ihttp://localhost:8000/api/accounts/A-1001

← Prev: 06 — Keycloak / IdP · Next: 08 — OPA →


📚 返回专栏目录