Envoy Basic Auth 过滤器实战指南:配置、源码原理与每路由鉴权
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
Basic Auth 是 Envoy 内置的 HTTP 过滤器,用于解析并校验 RFC 7617 为核心骨架,结合 Envoy 仓库内过滤器源码(basic_auth_filter.cc)、配置工厂(config.cc)、API 定义(basic_auth.proto)与单元测试(filter_test.cc),完整讲解过滤器配置、htpasswd 用户文件格式、认证判定流程、每路由级鉴权覆盖与统计指标,帮助你直接在 Envoy 中落地一套可复制的 HTTP Basic Auth 鉴权方案。
过滤器是什么
Basic Auth 过滤器是一个 HTTP解码路径过滤器(decoder filter),它拦截进入的请求头(decodeHeaders阶段),从 HTTPAuthorization头中提取用户名和密码,与过滤器配置中预置的用户名-密码列表逐一比对:
- 用户名和密码有效:请求被放行,继续传递到过滤链中的下一个过滤器;
- 用户名和密码无效或缺失:请求被拒绝,返回
401 Unauthorized响应,并附带符合 RFC 7617 的WWW-Authenticate挑战头。
从源码结构看,过滤器的核心实现位于source/extensions/filters/http/basic_auth/,通过BasicAuthFilter::decodeHeaders完成全部判定逻辑,过滤器注册名称为envoy.filters.http.basic_auth(见 well_known_names.h),对应扩展类型 URL 为type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth,API 版本为 v3(见 basic_auth.proto 中的[#extension: envoy.filters.http.basic_auth]标记)。
过滤器配置
type URL 与 users 字段
过滤器通过 HTTP 过滤链中的typed_config完成配置,type URL 固定为:
type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth核心字段users是一个"用户名-密码对"列表,用于校验请求Authorization头中的用户凭据。它的取值需要符合htpasswd工具的输出格式(Apache HTTP Server 的密码文件格式),users字段类型为config.core.v3.DataSource,既可以内联提供,也可以指向磁盘上的文件(详见 basic_auth.proto 第 37 行,且该字段被标记为sensitive,即敏感字段)。
最小可运行配置示例
以下配置来自 basic_auth_filter.rst,可直接放入http_filters过滤链(通常放在router过滤器之前):
http_filters: - name: envoy.filters.http.basic_auth typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: inline_string: |- user1:{SHA}hashed_user1_password user2:{SHA}hashed_user2_password注意inline_string中使用|-块标量保留换行格式:文件每行一条用户记录,格式为用户名:{SHA}哈希值。目前过滤器仅支持{SHA}格式(即 Base64 编码的 SHA-1 摘要),文档明确指出其他格式可能在未来加入。
更完整的字段级配置
除users外,BasicAuth消息还提供了四个可选字段(均定义于 basic_auth.proto 第 33~74 行):
| 字段 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
users | DataSource(敏感) | 必填 | htpasswd 格式的用户名-密码对,内联或引用文件 |
forward_username_header | string | 不转发 | 认证成功后,将用户名以该请求头名注入到转发给后端的请求中;留空则不转发 |
authentication_header | string | Authorization | 指定从哪个请求头读取 Basic 凭据;留空则读取Authorization |
allow_missing | bool | false | 为true时,缺失凭据(无Authorization头,或头不是Basicscheme)的请求被放行;但已携带 Basic 凭据的请求仍会严格校验 |
emit_dynamic_metadata | bool | false | 认证成功后,向动态元数据(dynamic metadata)写入 key 为username的字段,命名空间为过滤链中配置的过滤器名称 |
其中forward_username_header与authentication_header在 proto 中都带HTTP_HEADER_NAME校验规则(非严格模式),确保填写的必须是合法 HTTP 头名。一个同时使用这些字段的示例:
http_filters: - name: envoy.filters.http.basic_auth typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth users: filename: /etc/envoy/htpasswd.users forward_username_header: x-auth-user authentication_header: authorization allow_missing: false emit_dynamic_metadata: true需要说明的是:users使用filename时,文件内容由Config::DataSource::read在过滤器工厂创建时读取(见 config.cc 第 70 行),因此修改密码文件后需要触发配置热更新(如 xDS 推送或重启)才能生效;而 per-route 的users则是在路由匹配时按需读取(同文件第 88 行传入了true)。
htpasswd 用户文件格式:源码级解析规则
过滤器对用户列表的解析实现在readHtpasswd函数中(config.cc 第 16~63 行)。了解这些规则有助于你写出合法、不会导致配置加载失败的 htpasswd 文件:
- 逐行解析,使用
:作为用户名与密码哈希的分隔符; - 跳过空行以及
#开头的注释行(源码注释中保留了一个 TODO,未来可能考虑修剪行首行尾空格); - 格式非法(行内没有
:)会直接返回错误,错误信息为basic auth: invalid htpasswd format, username:password is expected; - 用户名或哈希为空同样报错;
- 重复用户名会报
duplicate users错误——用户表中不允许同名用户; - 哈希必须带
{SHA}前缀,否则报错unsupported htpasswd format: please use {SHA}; {SHA}之后的 Base64 字符串长度必须为 28 个字符(Base64 编码的 SHA-1 摘要恰好为 28 字节文本),长度不符直接报错。
因此一个合法的 htpasswd 文件形如:
# 注释行会被忽略 user1:{SHA}tESsBmE/yNY3lb6a0L6vVQEZNqw= user2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8=上面两个哈希值正是单元测试中使用的样例(filter_test.cc 第 22~23 行,分别对应明文密码test1与test2),可作为自测基准。
{SHA}哈希的生成方式与过滤器内部校验逻辑一一对应:computeSHA1(basic_auth_filter.cc 第 25~33 行)对明文密码计算 SHA-1 摘要,再对 20 字节的二进制摘要做 Base64 编码。你可以用任意支持 SHA-1 + Base64 的工具(例如htpasswd -bn user pass,或openssl dgst -sha1 -binary | base64)生成同格式的哈希。
常量时间比较
值得注意的实现细节是:密码比对使用了 OpenSSL 的CRYPTO_memcmp(basic_auth_filter.cc 第 124 行),且在比较前先校验长度是否一致(第 121~122 行)。这是一种常量时间比较(constant-time comparison)做法,可以降低基于响应时间差异的时序侧信道攻击风险;先比长度也能避免对长度不匹配的字符串进行无谓的哈希比较。
认证判定流程:源码调用链剖析
过滤器在请求头阶段(decodeHeaders)完成全部认证逻辑(basic_auth_filter.cc 第 48~110 行),完整流程如下:
- 解析每路由覆盖配置:通过
Http::Utility::resolveMostSpecificPerFilterConfig获取当前路由的FilterConfigPerRoute;若存在,则用路由级用户表替换全局用户表(第 49~54 行); - 定位认证头:若配置了
authentication_header,从该头读取凭据;否则读取标准Authorization头(第 56~61 行); - 凭据缺失处理:
Authorization头不存在时——若allow_missing为true直接放行,否则以no_credential_for_basic_auth拒绝(第 63~69 行); - Scheme 校验:凭据必须以
Basic前缀开头(区分大小写)。非 Basic scheme(如Bearer)时,allow_missing=true放行,否则以invalid_scheme_for_basic_auth拒绝(第 73~79 行); - Base64 解码:去掉
Basic前缀后,对剩余 token 做无填充 Base64 解码(第 81~84 行); - 切分用户名/密码:解码后的字符串格式为
username:password,以第一个:切分;找不到冒号则以invalid_format_for_basic_auth拒绝(第 86~94 行); - 凭据校验:
validateUser在用户表中查找用户名,对密码计算{SHA}哈希并与存储值做常量时间比较;失败以invalid_credential_for_basic_auth拒绝(第 96~99 行,实现见第 112~125 行); - 成功后处理:若配置了
forward_username_header,把用户名写入该请求头后转发给上游;若开启emit_dynamic_metadata,将username写入 stream 的动态元数据(命名空间为过滤链中的过滤器名称);allowed计数器 +1 并放行(第 101~109 行)。
401 响应与 WWW-Authenticate 挑战头
当认证失败时,onDenied(第 135~150 行)会:
- 使
denied计数器 +1; - 调用
sendLocalReply返回401 Unauthorized,响应体为对应的失败原因文本; - 构造
WWW-Authenticate: Basic realm="<原始URI>"响应头,其中 URI 由请求头重建(最多截取 256 字符,MaximumUriLength常量),让浏览器等客户端可以弹窗重新提示输入凭据; - 返回
StopIteration终止后续过滤器处理。
单元测试对此有明确断言(filter_test.cc 第 113~126 行):对http://host/的请求,失败响应的WWW-Authenticate头值为Basic realm="http://host/",同时携带details字段(invalid_credential_for_basic_auth等),这些 details 会被记录到访问日志,方便排查认证失败原因。
每路由(Per-Route)配置
Basic Auth 过滤器支持在路由、虚拟主机或加权集群级别覆盖认证配置,实现"同一条过滤链、不同路径不同鉴权策略"。其类型 URL 为:
type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRouteBasicAuthPerRoute消息仅有一个必填字段users(类型 DataSource,标记为敏感字段,见 basic_auth.proto 第 78~82 行),即只覆盖用户表,不覆盖其他字段。从源码看,路由级配置通过BasicAuthFilterFactory::createRouteSpecificFilterConfigTyped构建为FilterConfigPerRoute(config.cc 第 84~93 行),并在每次请求的decodeHeaders开头被解析(basic_auth_filter.cc 第 49~54 行)。
官方示例:定制用户 + 关闭鉴权
以下示例来自 basic_auth_filter.rst 的 Per-Route Configuration 章节,演示两种典型场景:为/admin路径定制专属用户,以及对/static前缀路径关闭认证:
route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { path: "/admin" } route: { cluster: some_service } typed_per_filter_config: envoy.filters.http.basic_auth: "@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute users: inline_string: |- admin:{SHA}hashed_admin_password - match: { prefix: "/static" } route: { cluster: some_service } typed_per_filter_config: envoy.filters.http.basic_auth: "@type": type.googleapis.com/envoy.config.route.v3.FilterConfig disabled: true - match: { prefix: "/" } route: { cluster: some_service }解读:
/admin路由:通过BasicAuthPerRoute覆盖该路径的用户表,只有admin用户能访问;/static前缀路由:使用type.googleapis.com/envoy.config.route.v3.FilterConfig且disabled: true,直接禁用该过滤器,静态资源无需认证;- 其余
/前缀路由:不配置typed_per_filter_config,回落到过滤链级别的全局用户表。
路由级配置遵循 Envoy 标准的"最具体配置优先"(most specific per filter config)解析语义,因此你可以按route -> virtual host -> weighted cluster的粒度层层覆盖用户表。全局过滤链级的users是兜底,只有未被路由配置覆盖的请求才使用它。
与 JWT 等其他认证方法组合(OR 语义)
allow_missing与emit_dynamic_metadata两个字段的组合,是 Basic Auth 过滤器与其他认证过滤器(如 JWT)叠加、实现"任一认证通过即可放行"(OR 语义)的关键,API 文档对此有明确设计说明(见 basic_auth.proto 第 52~73 行注释):
- 当
allow_missing为true时,缺失凭据或非 Basic scheme 的请求不会被拒绝,而是放行到链中后续的认证过滤器(例如 JWT 过滤器); - 但携带了 Basic 凭据的请求仍会严格校验,不会因为
allow_missing而被绕过; - 为了防止"所有认证过滤器都因缺失凭据而放行、导致请求完全未认证"的漏洞,需要配合
emit_dynamic_metadata: true——认证成功时过滤器把username写入动态元数据; - 再在链尾配置一个 RBAC 过滤器(rbac_filter.rst),根据动态元数据中是否存在
username来放行请求,从而保证至少有一种认证方式成功。
动态元数据的命名空间等于过滤链中该过滤器的名称(默认即envoy.filters.http.basic_auth),写入的 key 为username(见 basic_auth_filter.cc 第 127~133 行,DynamicMetadataUsernameKey常量)。单元测试验证了这一点:emit_dynamic_metadata开启时,认证成功后元数据中包含username: "user1",而默认关闭时不会写入任何元数据(filter_test.cc 第 62~98 行)。
统计指标
Basic Auth 过滤器在http.<stat_prefix>.basic_auth.命名空间下输出两项计数器统计(见 basic_auth_filter.rst 的 Statistics 章节,stat_prefix来自过滤链的通用配置前缀):
| 名称 | 类型 | 描述 |
|---|---|---|
allowed | Counter | 通过认证、被放行的请求总数 |
denied | Counter | 被拒绝的请求总数 |
这两项计数器的定义位于 basic_auth_filter.h 第 19~28 行的ALL_BASIC_AUTH_STATS宏,在FilterConfig构造时以stats_prefix + "basic_auth."为前缀注册(basic_auth_filter.cc 第 44 行)。递增时机分别在认证成功放行前(第 108 行)与onDenied中(第 137 行)。你可以通过这两个指标观察认证拒绝率,作为告警与容量规划的输入。
测试用例验证
仓库的单元测试与集成测试覆盖了过滤器的主要行为,可作为你验证自身配置行为的参照:
- filter_test.cc(465 行):覆盖正确凭据放行、错误用户拒绝、错误密码拒绝、
x-username转发头注入、动态元数据写入、401 响应头与 details 断言等; - config_test.cc:覆盖 htpasswd 解析的合法性校验(重复用户、非法格式、
{SHA}前缀、哈希长度等); - basic_auth_integration_test.cc:端到端验证过滤器在真实 HTTP 链路中的表现。
相关文件速查
- 官方文档:basic_auth_filter.rst
- API 定义:basic_auth.proto
- 过滤器实现:basic_auth_filter.cc、basic_auth_filter.h
- 配置工厂与 htpasswd 解析:config.cc
- 单元/集成测试:filter_test.cc、config_test.cc、basic_auth_integration_test.cc
结合官方配置示例与上述源码实现,你可以在 Envoy 中快速启用全局 Basic Auth 鉴权、按路由定制用户甚至局部禁用认证,并通过统计指标与访问日志中的 details 字段持续观测认证效果。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考