当前位置: 首页 > news >正文

Envoy Gateway + ext_authz 做“入口统一鉴权”,ABP 只做资源执行

Envoy Gateway + ext_authz 做“入口统一鉴权”,ABP 只做资源执行 🛡️⚙️


📚 目录

  • Envoy Gateway + ext_authz 做“入口统一鉴权”,ABP 只做资源执行 🛡️⚙️
    • 1) 背景 & 目标 🎯
    • 2) 架构与职责边界 🧭
      • 2.1 组件总览
      • 2.2 判定顺序 + 头部去向
    • 3) 环境与版本 🧪
    • 4) 网关:路由 + 统一鉴权 + 头透传 🧱
      • 4.1 业务入口:HTTPRoute
      • 4.2 SecurityPolicy(ext_authz:HTTP 模式)
      • 4.3 超时位置更正(⚠️重要)
      • 4.4 灰度/重试/熔断(集中治理)
      • 4.5 头部大小与风险
    • 5) 授权服务(ext_authz)🧠
      • 5.1 契约要点
      • 5.2 参考实现(HTTP,.NET Minimal · 任意路径+方法)
    • 6) ABP 集成:**只做“资源执行”**(行过滤 + 字段裁剪 + 审计)🧩
      • 6.1 中间件:验签 + 注入访问上下文
      • 6.2 绑定租户(ICurrentTenant)& 与 ABP 默认 `__tenant` 的协同
      • 6.3 行级过滤(RowFilter → 动态表达式/全局过滤器)
      • 6.4 字段裁剪(查询阶段投影)——优先避免拉回无用列
      • 6.5 应用服务组合 🎛️
    • 7) 失败与回退(Failure Mode)🧯
    • 8) 可观测性与 SLO 📊
    • 9) 压测(k6)🏎️
    • 10) 版本/兼容性提示 🧩
      • FAQ 💡


1) 背景 & 目标 🎯

  • 现状痛点:服务内鉴权 → 重复实现标准不一、高并发下抖动放大
  • 策略:PDP/PEP 分离,把判定(ext_authz)前移至网关,后端聚焦“执行”。
  • 交付:YAML + .NET 代码 + k6 压测脚本 一把跑通;观测/灰度/回退全链路可操作。✅

2) 架构与职责边界 🧭

2.1 组件总览

HTTP
ext_authz 鉴权
允许: headersToBackend 透传
拒绝: 403/401
Client
Envoy Gateway
ext_authz Service (HTTP/gRPC)
ABP Service
DB: EF Core
Audit/Logs

2.2 判定顺序 + 头部去向

ClientEnvoy Gatewayext_authz (HTTP/gRPC)ABP ServiceDB发往授权服务的头(HTTP/gRPC) ≠ 回传给后端的头"headersToBackend" 仅决定**允许时**哪些授权响应头会透传到后端HTTP /orders1Check(method/path/headers/claims...)2401/4033401/4034200 + 响应头(x-tenant-id/x-field-mask/x-row-filter/x-authz-signature)5上游请求 + 白名单透传(headersToBackend)6验签 + 绑定租户(ICurrentTenant)7EF(全局过滤+动态Where) + DTO投影(字段裁剪)8200 + 数据9alt[Deny][Allow]ClientEnvoy Gatewayext_authz (HTTP/gRPC)ABP ServiceDB

3) 环境与版本 🧪

  • Kubernetes:1.26+

  • Envoy Gateway v1.5.3(Helm 安装 + Quickstart)

    helm install eg oci://docker.io/envoyproxy/gateway-helm \--version v1.5.3 \-n envoy-gateway-system --create-namespacekubectl apply -f \https://github.com/envoyproxy/gateway/releases/download/v1.5.3/quickstart.yaml \-n default
    
  • .NET 8/9(ABP vNext);EF Core 为数据访问

  • 观测:使用官方 Addons Helm Chart 安装 Prometheus / Grafana / OTEL(版本与 EG 对齐)📈


4) 网关:路由 + 统一鉴权 + 头透传 🧱

4.1 业务入口:HTTPRoute

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:name: orders
spec:parentRefs:- name: eghostnames:- "api.example.com"rules:- matches:- path:type: PathPrefixvalue: /ordersbackendRefs:- name: abp-ordersport: 8080

4.2 SecurityPolicy(ext_authz:HTTP 模式)

关键:headersToBackend 决定允许时授权响应中的哪些头会被透传给后端

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:name: orders-ext-auth
spec:targetRefs:- group: gateway.networking.k8s.iokind: HTTPRoutename: ordersextAuth:http:backendRefs:- name: http-ext-auth       # 你的授权服务port: 9002headersToBackend:- x-current-user- x-tenant-id- x-field-mask- x-row-filter- x-authz-signature

4.3 超时位置更正(⚠️重要)

  • HTTPRoute 层配置超时(而非 BackendTrafficPolicy):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:name: orders
spec:parentRefs:- name: eghostnames: ["api.example.com"]rules:- matches:- path: { type: PathPrefix, value: /orders }timeouts:request: 5s          # 端到端backendRequest: 3s   # 单次上游请求backendRefs:- name: abp-ordersport: 8080

推荐 request ≥ backendRequest,避免“阴影超时”。

4.4 灰度/重试/熔断(集中治理)

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:name: btp-orders
spec:targetRefs:- group: gateway.networking.k8s.iokind: HTTPRoutename: ordersretry:numRetries: 2circuitBreaker:maxConnections: 1024maxPendingRequests: 1024

4.5 头部大小与风险

base64后仍大
大JSON 掩码/过滤
请求头 > 60 KiB
HTTP 431
规避: 传引用ID/指纹 + 后端缓存
或调高 max_request_headers_kb(权衡)

5) 授权服务(ext_authz)🧠

5.1 契约要点

  • 允许:返回 200 OK,在响应头放入 x-tenant-id/x-field-mask/x-row-filter/x-authz-signature/...
  • 拒绝:返回 401/403
  • 转发到授权服务的请求头:与 headersToBackend 不同且依实现/版本而异(尤其 HTTP vs gRPC);对关键头(Authorization/Cookie/X-Forwarded-*)请做集成测试或显式配置,确保到达授权服务。
  • HTTP 模式路径与方法:网关通常沿用原请求的方法与路径去调用授权服务(不是固定 /check),因此服务端需匹配任意路径与方法;或改用 gRPC ext_authz

5.2 参考实现(HTTP,.NET Minimal · 任意路径+方法)

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();const string Secret = "change_me";// 显式匹配所有常见方法 + 任意路径
string[] verbs = new[] { "GET","POST","PUT","PATCH","DELETE","OPTIONS","HEAD" };
app.MapMethods("/{**path}", verbs, (HttpContext ctx) =>
{// 1) 检查凭证(示例)var auth = ctx.Request.Headers.Authorization.ToString();if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))return Results.StatusCode(403);// 2) 业务判定(示例)var user = "user1";var tenantId = "t-1001";var fieldMask = "Order:Id,No,Total;Item:Sku,Qty";var rowFilter = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("""{"TenantId":"t-1001"}"""));// 3) 允许时:把要透传给后端的头写在**响应头**中(需在 headersToBackend 白名单)ctx.Response.Headers.Append("x-current-user", user);ctx.Response.Headers.Append("x-tenant-id", tenantId);ctx.Response.Headers.Append("x-field-mask", fieldMask);ctx.Response.Headers.Append("x-row-filter", rowFilter);// 4) HMAC 签名(常量时间比较;可扩展 ts/nonce)var canonical = $"{ctx.Request.Method}\n{ctx.Request.Path}";using var mac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(Secret));var sig = Convert.ToHexString(mac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant();ctx.Response.Headers.Append("x-authz-signature", sig);return Results.Ok();
});app.Run("http://0.0.0.0:9002");

6) ABP 集成:只做“资源执行”(行过滤 + 字段裁剪 + 审计)🧩

6.1 中间件:验签 + 注入访问上下文

public record AccessContext(string? TenantId, string? FieldMask, string? RowFilterBase64);
public interface IAccessContextAccessor { AccessContext Current { get; set; } }
public class AccessContextAccessor : IAccessContextAccessor
{ public AccessContext Current { get; set; } = new(null,null,null); }public class AuthzHeadersMiddleware
{private readonly RequestDelegate _next;private const string Secret = "change_me";public AuthzHeadersMiddleware(RequestDelegate next) => _next = next;public async Task Invoke(HttpContext ctx, IAccessContextAccessor accessor){var sig = ctx.Request.Headers["x-authz-signature"].ToString();var canonical = $"{ctx.Request.Method}\n{ctx.Request.Path}";using var h = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(Secret));var expected = Convert.ToHexString(h.ComputeHash(System.Text.Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant();var ok = !string.IsNullOrEmpty(sig) &&System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(Convert.FromHexString(sig), Convert.FromHexString(expected));if (!ok) { ctx.Response.StatusCode = 401; await ctx.Response.WriteAsync("Invalid authz signature"); return; }var tenantId = ctx.Request.Headers["x-tenant-id"].ToString();var fieldMask = ctx.Request.Headers["x-field-mask"].ToString();var rowFilter = ctx.Request.Headers["x-row-filter"].ToString();accessor.Current = new AccessContext(tenantId, fieldMask, rowFilter);await _next(ctx);}
}

6.2 绑定租户(ICurrentTenant)& 与 ABP 默认 __tenant 的协同

public class TenantBindingMiddleware
{private readonly RequestDelegate _next;public TenantBindingMiddleware(RequestDelegate next) => _next = next;public async Task Invoke(HttpContext ctx, IAccessContextAccessor accessor, ICurrentTenant currentTenant){// 以网关判定为准;如与 Token Claim 冲突,可拒绝或降级只读(在此处处理)using (currentTenant.Change(accessor.Current.TenantId)){await _next(ctx);}}
}// 注册
builder.Services.AddSingleton<IAccessContextAccessor, AccessContextAccessor>();
app.UseMiddleware<AuthzHeadersMiddleware>();
app.UseMiddleware<TenantBindingMiddleware>();

ABP 默认头名是 __tenant;如果你使用 x-tenant-id,请自定义解析器或在中间件绑定到 ICurrentTenant

6.3 行级过滤(RowFilter → 动态表达式/全局过滤器)

public static class RowFilterExtensions
{public static IQueryable<T> ApplyRowFilter<T>(this IQueryable<T> query, string? rowFilterBase64){if (string.IsNullOrWhiteSpace(rowFilterBase64)) return query;var json = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(rowFilterBase64));var dict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new();var allow = new HashSet<string>(StringComparer.OrdinalIgnoreCase){ "TenantId", "CustomerId" }; // 白名单var p = Expression.Parameter(typeof(T), "e");Expression? body = null;foreach (var (k,v) in dict){if (!allow.Contains(k)) continue;var prop = Expression.PropertyOrField(p, k);var constant = Expression.Constant(Convert.ChangeType(v, prop.Type));var eq = Expression.Equal(prop, constant);body = body == null ? eq : Expression.AndAlso(body, eq);}if (body == null) return query;var lambda = Expression.Lambda<Func<T,bool>>(body, p);return query.Where(lambda);}
}

6.4 字段裁剪(查询阶段投影)——优先避免拉回无用列

public static class FieldMaskExtensions
{public static IQueryable<dynamic> ApplyFieldMask<T>(this IQueryable<T> query, string? mask){if (string.IsNullOrWhiteSpace(mask)) return query.Select(x => (dynamic)x)!;var parts = mask.Split(':', 2);if (parts.Length != 2) return query.Select(x => (dynamic)x)!;var fields = parts[1].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);var safe = fields.Where(f => typeof(T).GetProperty(f) != null).ToArray(); // 白名单校验if (safe.Length == 0) return query.Select(x => (dynamic)x)!;var select = $"new({string.Join(",", safe)})"; // System.Linq.Dynamic.Corereturn query.Select(select);}
}

6.5 应用服务组合 🎛️

public class OrderAppService : ApplicationService
{private readonly IRepository<Order, Guid> _repo;private readonly IAccessContextAccessor _access;public OrderAppService(IRepository<Order, Guid> repo, IAccessContextAccessor access)=> (_repo, _access) = (repo, access);public async Task<List<object>> GetListAsync(){var q = await _repo.GetQueryableAsync();q = q.ApplyRowFilter(_access.Current.RowFilterBase64);            // 行过滤var projected = q.ApplyFieldMask<Order>(_access.Current.FieldMask); // 字段裁剪(查询阶段)return await projected.ToDynamicListAsync();}
}

7) 失败与回退(Failure Mode)🧯

ext_authz 超时/异常
failure_mode_allow = false
failure_mode_allow = true
鉴权恢复(AuthHealthy)
故障恢复
Running
ExtAuthTimeout
StrictDeny
SoftAllow
  • 严格failure_mode_allow=false(默认),网关直接 4xx/5xx,并记录 decision_id
  • 宽松true 时可“最低权限放行”(慎用),加 x-authz-downgraded: true 标识;
  • 熔断/超时:用 BackendTrafficPolicy(熔断/重试)+ HTTPRoute.timeouts(超时)对齐端到端参数。

8) 可观测性与 SLO 📊

Metrics
Proxy Metrics (Envoy)
Gateway Metrics
Prometheus
Grafana Dashboards
OTel Collector
APM/Tracing
  • 重点:ext_authz 延迟/错误率、401/403/429、熔断打开率、各 Route P95/错误分布、请求头大小直方图
  • 审计:全链路打通 decision_id(ext_authz → 网关 → ABP),支持回放。

9) 压测(k6)🏎️

// k6 run authz.js
import http from 'k6/http';
import { check, sleep } from 'k6';export let options = {vus: 100, duration: '60s',thresholds: { http_req_failed: ['rate<0.01'], http_req_duration: ['p(95)<200'] },
};export default function () {const host = __ENV.GATEWAY_HOST;const res = http.get(`http://${host}/orders`, {headers: { 'Host': 'api.example.com', 'Authorization': 'Bearer x' }});check(res, { 'status is 200': (r) => r.status === 200 || r.status === 304 });sleep(0.2);
}
  • 场景:无鉴权 → 启用 ext_authz(缓存命中/未命中)→ 注入掩码/过滤 → 故障注入(超时/熔断)。
  • 观测:P95/P99、拒绝率、熔断命中、头大小分布是否逼近 60KiB 门槛。

10) 版本/兼容性提示 🧩

  • 仅在必要时开启“路由重算”(如果授权后新增/修改的请求头会影响路由匹配),否则不启用。
  • 发往授权服务的请求头请做集成测试(Authorization/Cookie/X-Forwarded-*…),必要时显式配置以确保到达;与 headersToBackend 概念区分开。
  • ABP 默认租户头__tenant;若采用 x-tenant-id,需自定义解析器或在中间件绑定到 ICurrentTenant
  • 请求头体量:默认 60 KiB;尽量传引用/指纹,把大对象放后端缓存以按 decision_id 再取详情。

FAQ 💡

  • Q:HTTP 与 gRPC ext_authz 都能“允许时添加上游请求头”吗?
    A:是。HTTP 模式依赖 ext_authz 过滤器把授权响应头合并进上游请求,再由 headersToBackend 决定透传给后端;gRPC 按 proto 返回 headers_to_add 等。

  • Q:是否开启失败放行(failure_mode_allow)?
    A:默认不开。仅对读接口、可控风险的场合灰度开启,并在指标/日志中清晰打标与告警。

  • Q:行过滤与字段裁剪放哪一层?
    A:行过滤优先 EF Core(全局过滤 + 动态 Where);字段裁剪优先查询阶段投影,避免把无用列拉回后再删。

http://www.dtcms.com/a/473424.html

相关文章:

  • vscode免密码认证ssh连接virtual box虚拟机
  • 3.6 JSON Mode与JSON Schema
  • React Native::关于react的匿名函数
  • 基于JETSON ORIN+FPGA+GMSL AI相机的工业双目视觉感知方案
  • 常规的鱼眼镜头有哪些类型?能做什么?
  • 虚实之间:AR/VR开发中的性能优化艺术
  • 新手要如何让网站被收录公司查询信息查询
  • PostgreSQL 的 hstore、arrays 数据类型
  • Java集合体系 —— Set篇
  • 硅基计划5.0 MySQL 贰 SQL约束三大范式
  • 设计模式——工厂模式
  • 变色龙哈希与隐私保护
  • 栈和队列:“单端吞吐”VS”双端通行“(第十讲)
  • ros2系统在ubuntu18.04环境下的环境搭建
  • 个人网站展示dw网站制作
  • 鸿蒙NEXT系列之精析NDK UI API(节点增删和属性设置)
  • 10个免费货源网站郑州网络科技公司有哪些
  • Spring 源码学习(十三)—— RequestMappingHandlerAdapter
  • 虚幻引擎虚拟制片入门教程 之 3D渲染基础知识:模型、材质、贴图、UV等
  • excel导出使用arthas动态追踪方法调用耗时后性能优化的过程
  • 【数据结构】强化训练:从基础到入门到进阶(2)
  • python异步编程 -什么是python的异步编程, 与多线程和多进程的区别
  • Linux系统--进程间通信--共享内存相关指令
  • 网站开发的实践报告石家庄市工程勘察设计咨询业协会
  • TensorFlow深度学习实战——图分类
  • SAP MM采购信息记录维护接口分享
  • 网站搭建装修风格大全2021新款简约
  • Mysql初阶第八讲:Mysql表的内外连接
  • SpringCloud 入门 - Gateway 网关与 OpenFeign 服务调用
  • uniapp 选择城市(城市列表选择)