07 — Kong网关 / PEP GitHub: https://github.com/geekchow/micro-service-authKong 是 API 网关也是策略执行点PEP它拦截每个请求、用Keycloak校验令牌、向OPA请求授权决策并把被允许的请求转发给banking-api-service。什么是 Kong在本 PoC 所用的 IdP / PEP / PDP 分工中Keycloak是IdP—— 它签发令牌。Kong是PEP—— 它在边缘执行访问控制。OPA是PDP—— 它判定某个请求是否被允许。banking-api-service是资源服务器—— 它拥有受保护的银行数据。Kong 在本项目中的职责接收传入的 API 请求。拒绝缺失或格式错误的Authorization头的请求。调用Keycloak内省确认令牌当前处于 active。从令牌载荷中解码 JWT 声明。用请求路径、方法与声明构建一个 input 对象。调用OPA获取授权决策。若OPA拒绝则返回403若OPA允许则转发给banking-api-service。Kong 不是身份提供方、不是策略引擎、也不是银行业务服务。两个与 Kong 相关的配置文件本仓库中有两个文件控制 Kong文件用途docker-compose.yml容器串联 —— 镜像、端口、环境变量、卷、依赖infra/kong/kong.ymlKong 运行时行为 —— service、route、plugin、plugin 配置值它们之间的关系docker-compose.ymlKong containerinfra/kong/kong.ymlinfra/kong/plugins/opa-authzbanking-api-serviceKeycloakOPAdocker-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:8001depends_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_DATABASEoff—— Kong 以无数据库DB-less模式运行。所有配置来自文件而非数据库。KONG_DECLARATIVE_CONFIG/etc/kong/kong.yml—— 保存 Kong 声明式路由配置的文件。KONG_PLUGINSbundled,opa-authz—— 启用 Kong 内置插件以及自定义的opa-authz插件。KONG_ADMIN_LISTEN0.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-service与opa启动之后才启动。若banking-api-service重启Kong 也会重启restart: true。infra/kong/kong.yml中的 Kong该文件使用 Kong 的声明式格式_format_version: 3.0并定义了一个 service内联一个 route 与一个 plugin_format_version:3.0services:-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:2000Servicename: banking-api—— Kong 对上游的内部名称。url: http://banking-api-service:8080—— Kong 把被允许的请求转发到这里。Routepaths: [/api/accounts]—— 以/api/accounts开头的请求匹配此路由。strip_path: false—— 路径按原样转发。因此一个GET /api/accounts/A-1001的请求来自alice或ops-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 在中止前等待Keycloak或OPA的时长。opa-authz插件自定义插件位于infra/kong/plugins/opa-authz/由两个文件组成schema.lua—— 配置定义schema.lua声明了合法插件配置的形态。Kong 在启动时会用此 schema 校验任何opa-authz插件块return{nameopa-authz,fields{{config{typerecord,fields{{opa_url{typestring,requiredtrue}},{introspection_url{typestring,requiredtrue}},{introspection_client_id{typestring,requiredtrue}},{introspection_client_secret{typestring,requiredtrue}},{timeout_ms{typenumber,default2000}},},},},},}四个 URL/凭据字段都是必填字符串 —— 任一缺失Kong 都会拒绝启动。timeout_ms是可选的默认2000。schema.lua定义什么是被允许的kong.yml提供实际值handler.lua在运行时使用它们。handler.lua—— 执行逻辑handler.lua在 Kong 的access阶段优先级900对每个匹配的请求运行。逻辑按以下顺序执行第 1 步 —— 读取 bearer 令牌localauth_headerkong.request.get_header(authorization)ifnotauth_headerthenreturnkong.response.exit(401,{messagemissing bearer token})endlocaltokenauth_header:match([Bb]earer%s(.))ifnottokenthenreturnkong.response.exit(401,{messageinvalid bearer token})end若不存在Authorization头、或其中不含 bearer 令牌则返回401。第 2 步 —— 用Keycloak内省令牌localintrospection,introspection_errintrospect_token(conf,token)ifnotintrospectionthenreturnkong.response.exit(503,{messageintrospection unavailable,detailintrospection_err})endifintrospection.active~truethenreturnkong.response.exit(401,{messageinactive token})endKong 用introspection_client_id与introspection_client_secret以 HTTP Basic 认证把令牌 POST 到Keycloak的内省端点。若Keycloak不可达Kong 返回503。若令牌不是 activeKong 返回401。关于 Kong 为何使用内省而非基于 JWKS 的本地校验见下文「为什么 Kong 使用内省而非 JWKS」。关于内省机制上如何工作见 11 — JWT 签名、校验与内省。第 3 步 —— 解码 JWT 声明localclaimsdecode_claims(token)ifnotclaimsthenreturnkong.response.exit(401,{messageunreadable jwt payload})endKong 对 JWT 载荷段做 base64 解码并 JSON 解析以读取声明。这是一次本地解码 —— 没有额外的网络调用。第 4 步 —— 确定有效角色localfunctioneffective_role(claims)localrealm_accessclaimsandclaims.realm_accesslocalrolesrealm_accessandrealm_access.rolesor{}for_,roleinipairs(roles)doifroleops-adminthenreturnops-adminendendfor_,roleinipairs(roles)doifrolecustomerthenreturncustomerendendreturnnilendops-admin优先于customer。若两个角色都不存在effective_role返回nil。第 5 步 —— 构建 OPA input 并调用OPAlocalaccount_idkong.request.get_path():match(/api/accounts/([^/]))localrequest_bodycjson.encode({input{methodkong.request.get_method(),pathkong.request.get_path(),account_idaccount_id,customer_idclaim_value(claims.customer_id),account_idsclaim_values(claims.account_ids),roleeffective_role(claims),usernameclaims.preferred_username,},})对于像alice调用GET /api/accounts/A-1001这样的请求Kong 从路径中提取account_id A-1001并把它与其他字段打包成一个 JSON input 文档。第 6 步 —— 执行OPA决策ifdecision.result~truethenreturnkong.response.exit(403,{messageforbidden})endOPA返回{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-serviceKong 只转发那些同时通过了Keycloak内省与OPA授权的请求。banking-api-service不直接对外暴露 —— 来自alice或ops-admin的所有流量都经由 Kong 到达。banking-api-service也会用 JWKS 自行校验 JWT。这是纵深防御银行服务不会因为 Kong 已经检查过就完全信任。Kong 与identity-bootstrap-serviceKong 并不位于identity-bootstrap-service之前。bootstrap 是一个内部演示工具位于同一 Compose 网络上但 Kong 不向它路由任何流量。Kong 与KeycloakKong 使用Keycloak做令牌内省而不是把它当作自身管理访问的身份提供方。为什么 Kong 使用内省而非 JWKSKong 是边缘 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 与OPAKong 仅在令牌被确认 active 之后才调用OPA。OPA接收到字段来源method请求的 HTTP 方法path请求路径account_id从/api/accounts/{account_id}提取customer_idJWT 声明account_idsJWT 声明列表role推导得到ops-admin|customer|nilusernameJWT 的preferred_username声明OPA返回{result: true}或{result: false}。Kong 据此执行。把策略放在OPA里意味着授权规则可以在不修改 Kong 或银行服务的情况下变更。实用检查命令从 Compose 网络内部发起请求时使用辅助容器dockercomposeexeccurlsh检查Keycloak的 OIDC discoverydockercomposeexeccurlcurlhttp://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 → 返回专栏目录