什么是 oauth2-proxy?

oauth2-proxy 是一个开源的、轻量级的身份感知反向代理。它的工作很简单:拦在应用前面,检查每个 HTTP 请求是否带着有效会话;没有的话,把用户导向 OAuth2/OIDC Provider(比如 Keycloak、GitHub、Google、Dex)去登录,登录成功后再放行。

它不存储用户、不管理密码、不签发 Token——这些都是 Provider 的事。oauth2-proxy 只做一件事:把没有身份的人挡在门外,把有身份的人放进来,并把用户信息(邮箱、用户名、组)通过 HTTP Header 传给后端应用

为什么需要它?

在 IDaaS 体系里,认证和授权是 IDP 的事,但你的应用可能根本没写登录逻辑。oauth2-proxy 的价值在于:

  • 零代码改造:给老应用、静态站点、容器化服务加 OIDC 登录,不用改一行业务代码。
  • 入口层统一:Ingress/反向代理层统一拦截,不用每个服务自己实现 OAuth 回调、Token 刷新、Session 管理。
  • Provider 无关:同一个 oauth2-proxy 可以对接 Keycloak、Azure AD、GitHub、Google、Dex 等几十种 Provider,切换 Provider 不影响后端。

架构概览

  sequenceDiagram
    participant Browser as 浏览器
    participant Ingress as Nginx Ingress/Traefik
    participant O2P as oauth2-proxy
    participant App as 后端应用
    participant IDP as Keycloak (IDP)

    Browser->>Ingress: GET /app
    Ingress->>O2P: GET /oauth2/auth (auth_request)
    
    alt 无有效 Session Cookie
        O2P-->>Ingress: 401 → /oauth2/start
        Ingress-->>Browser: 302 /oauth2/start
        Browser->>O2P: GET /oauth2/start
        O2P-->>Browser: 302 → IDP 登录页
        Browser->>IDP: 登录
        IDP-->>Browser: 302 /oauth2/callback?code=...
        Browser->>O2P: GET /oauth2/callback
        O2P->>IDP: 用 code 换 token
        IDP-->>O2P: access_token + id_token
        O2P-->>Browser: Set-Cookie + 302 回到 /app
        Browser->>Ingress: GET /app (带 Cookie)
        Ingress->>O2P: GET /oauth2/auth (auth_request)
    end

    O2P-->>Ingress: 202 Accepted
    Note over O2P,Ingress: X-Auth-Request-User<br/>X-Auth-Request-Email<br/>X-Auth-Request-Groups
    Ingress->>App: GET /app + 用户身份 Header
    App-->>Browser: 200 OK

流程要点:

  1. 所有请求先经过 oauth2-proxy 的 /oauth2/auth 端点校验 Session Cookie。
  2. 没有有效 Cookie → 302 跳 /oauth2/start → 302 跳 IDP 登录页。
  3. 登录成功后回到 /oauth2/callback,oauth2-proxy 用授权码换 Token,写入 Session Cookie。
  4. 后续请求携带 Cookie,oauth2-proxy 校验通过后返回 202,并在响应头中注入 X-Auth-Request-UserX-Auth-Request-EmailX-Auth-Request-Groups 等身份信息。
  5. Nginx Ingress(通过 auth_request)或 Traefik(通过 ForwardAuth)透传这些响应头到后端。

认证响应头不是自动传到业务后端的

这是 auth-url 集成里最容易被误判的边界:--set-xauthrequest=true 只让 oauth2-proxy 在 /oauth2/auth 的响应中生成 X-Auth-Request-*;它不会替业务 Ingress 把 Header 复制到最终请求。以 ingress-nginx 为例,必须同时配置认证 URL、401 跳转和允许复制的响应头:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
    nginx.ingress.kubernetes.io/auth-response-headers: >-
      X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Groups

只列出后端确实需要的 Header。X-Auth-Request-* 不是客户端可自行声明的身份凭证:入口必须覆盖或清理客户端同名 Header,后端 Service 也应只允许来自入口的流量,否则用户可以直接伪造 X-Auth-Request-Groups。如果后端要验证 OAuth access token,另行开启 --pass-access-token=true 并透传 X-Auth-Request-Access-Token;不要把 --pass-authorization-header 产生的 ID Token 当成资源服务器的 access token。

验证时分两段看,避免把“认证成功”误认为“身份已到达业务:

# 认证代理:确认生成认证响应头的开关
kubectl get deploy -n auth oauth2-proxy -o jsonpath='{.spec.template.spec.containers[0].args}' \
  | jq -r '.[]' | grep -E 'set-xauthrequest|pass-access-token|pass-authorization-header'

# 入口:确认 auth-url、跳转和响应头复制均存在
kubectl get ingress -n app internal-app -o yaml \
  | grep -E 'auth-url|auth-signin|auth-response-headers'

依据: oauth2-proxy Header Optionsingress-nginx 外部认证示例

核心概念

Provider

oauth2-proxy 不自己实现认证,而是委托给上游 Provider。它支持的 Provider 包括:

类别Provider关键参数
企业 IDPKeycloak OIDC、Azure AD、Okta、GitLab--provider=keycloak-oidc,需配置 issuer、client-id、client-secret
社交登录Google、GitHub、Facebook、LinkedIn--provider=google,OAuth 2.0 授权码流程
通用 OIDC任意 OIDC Provider--provider=oidc,需提供 --oidc-issuer-url
轻量 IDPDex(带 OIDC connector)--provider=oidc,issuer 指向 Dex 的 discovery URL

oauth2-proxy 的会话管理完全基于 Cookie:

  • Session Cookie(默认 _oauth2_proxy):存储加密后的 access token、ID token、refresh token 和过期时间。
  • CSRF Cookie(默认 _oauth2_proxy_csrf):防止跨站请求伪造,登录流程中校验 state 与 CSRF token 的一致性。

配置来源的优先级是 命令行参数 > 环境变量 > 配置文件。这条规则在 Kubernetes 中很容易被 Helm values 和 Secret 覆盖:排查“配置看起来正确但进程行为不对”时,先检查最终容器参数和环境变量,不要只看挂载的配置文件。

Cookie Secret 必须是解码后 16、24 或 32 字节的值。生产环境优先使用官方文档给出的 URL-safe Base64 生成方式,避免把普通 Base64 的 +/ 或换行直接复制进 Secret:

python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'

Cookie 关键配置项:

参数作用生产建议
--cookie-secretAES 加密密钥(16/24/32 字节)生成随机串,所有 oauth2-proxy 实例共用同一值
--cookie-secure=true仅 HTTPS 传 Cookie生产必须开
--cookie-httponly=true禁止 JS 读取 Cookie必须开
--cookie-samesite=laxSameSite 策略lax 平衡安全与可用性;纯 API 场景可用 strict
--cookie-domainCookie 作用域跨子域共享 Cookie 时设置,如 .example.com
--cookie-expire=24hCookie 有效期内部工具设 8-24h;敏感系统设 1-4h

默认 cookie Session Store 并不要求单副本或会话亲和性:多副本只要共享相同的 --cookie-secret 即可解密会话。切换到 redis 后,Cookie 通常只携带服务端 Session ID,但 Redis 的 TLS、凭据、容量、故障切换和恢复演练就成为 IAM 网关的生产前置条件。选择 Redis 的理由应是 Cookie 体积、服务端撤销或集中会话管理,而不是“副本多了就必须上 Redis”。详见 IAM 网关 oauth2-proxy 常见错误排错

Upstream

oauth2-proxy 本身不处理业务流量,它有两个端口:

端口职责
4180(默认)HTTP 代理端口:接收 /oauth2/auth/oauth2/callback/oauth2/start/oauth2/sign_out
无(auth_request 模式)作为 Nginx Ingress 的 auth_request 后端或 Traefik ForwardAuth 后端时,只接受内网调用

部署模式

模式 1:Sidecar(推荐)

每个应用 Pod 旁挂一个 oauth2-proxy sidecar 容器,与业务容器共享 localhost 网络:

# Deployment 摘录
spec:
  containers:
  - name: app
    image: my-app:latest
    ports:
    - containerPort: 8080
  - name: oauth2-proxy
    image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
    args:
    - --http-address=0.0.0.0:4180
    - --upstream=http://localhost:8080
    - --provider=keycloak-oidc
    - --oidc-issuer-url=https://sso.example.test/realms/internal
    - --client-id=my-app
    - --client-secret=***  # 建议通过 Secret 环境变量注入
    - --cookie-secret=***  # 同上
    - --email-domain=*
    - --cookie-secure=true
    - --reverse-proxy=true
    ports:
    - containerPort: 4180

优点:生命周期与应用一致,端口不暴露到集群外。缺点:每个应用一个 oauth2-proxy,资源碎片化。

模式 2:独立 Service(多应用共享)

一个 oauth2-proxy Deployment/Service 保护多个应用:

# oauth2-proxy 配置片段
--upstream=static://200  # 不代理具体 upstream,仅做 auth_request 校验
--skip-auth-route=GET=^/healthz  # 健康检查跳过认证

多个 Ingress 都指向同一个 auth-url: http://oauth2-proxy.platform.svc/oauth2/auth

优点:资源集中,管理方便。缺点:所有应用复用同一个 Provider 和 Client,授权粒度受限;需要按应用设置 --allowed-group 时必须用不同实例。

模式 3:全代理模式(不推荐生产)

oauth2-proxy 同时做认证流量代理:用户浏览器 → oauth2-proxy:4180 → 后端应用。适合测试环境,生产环境缺少 Ingress 的 TLS 终结、限流、WAF 等能力。

与 Keycloak 对接的关键细节

oauth2-proxy 和 Keycloak 是最常见组合,但有几个容易踩坑的点:

Audience(aud claim)

Keycloak 默认不把 Client ID 写入 access token 的 aud 字段。oauth2-proxy 的 Keycloak OIDC Provider 会校验 aud,并要求其中包含 --client-id--oidc-extra-audience 配置的值;不匹配时返回:

{"error": "invalid_token", "error_description": "expected audience \"oauth2-proxy\" got [\"account\"]"}

解决方案:在 Keycloak Client 的 Client Scopes 里添加 Audience mapper,把 Included Client Audience 设为目标 Client ID。

Issuer URL(iss claim)

oauth2-proxy 的 --oidc-issuer-url 必须与 Keycloak 实际签发 Token 的 iss 完全一致:

Keycloak 版本典型 issuer URL
17+ 新部署https://sso.example.test/realms/internal
旧版(16-)https://sso.example.test/auth/realms/internal
配置了 --hostname-urlhostname-url 一致

不一致时,oauth2-proxy 会拒绝 Token:"issuer mismatch"

Groups vs Roles

oauth2-proxy 的 --allowed-group 可以限制哪些用户组能访问应用。Keycloak 端的组信息需要通过 Group Membership mapper(放在 Client Scope 或 Client 的 Dedicated Scope)写入 Token 的 groups claim。

如果使用 realm roles 控制访问,可以用 --allowed-role,但这种方式在跨 Client 共享时容易混淆,推荐优先用 groups。

  • redirect URL--redirect-url 必须与 Keycloak Client 的 Valid Redirect URIs 完全一致,通常不显式设置,让 oauth2-proxy 自动推导。
  • Cookie domain:如果 Keycloak 和业务应用不在同一个子域,注意 Cookie 作用域。--cookie-domain=.example.test 可以让 app.example.testsso.example.test 共享 Cookie(安全严格环境中不推荐)。

安全加固

Cookie 内容用 AES 加密,密钥通过 --cookie-secret 传入。生产环境:

  • 至少 32 字节随机值:openssl rand -base64 32
  • 所有 oauth2-proxy 实例共用同一个 secret(否则无法解密彼此的 Cookie)
  • 通过 Kubernetes Secret 挂载环境变量,不写入配置文件
  • 轮换流程:生成新 secret → 同时用新 secret 重启所有实例 → 所有用户需重新登录

超时与刷新

--cookie-expire=8h          # Session Cookie 有效期
--cookie-refresh=1h         # Token 刷新间隔
--session-store-type=cookie # Session 存储在 Cookie 中(默认,无需 Redis)

cookie-refresh 小于 cookie-expire 时,oauth2-proxy 会在接近过期时自动用 refresh token 续期,用户无感。

白名单路径

健康检查、静态资源、公开页面需要跳过认证:

--skip-auth-route=GET=^/healthz
--skip-auth-route=GET=^/metrics
--skip-auth-route=GET=^/public/.*

安全修复(v7.11.0)skip_auth_routes 的正则此前匹配完整请求 URI(路径 + 查询参数),可被构造为 /api/private/sensitive?path=/status 绕过认证。v7.11.0 修正为只匹配路径部分。如果你的白名单规则依赖查询参数匹配,升级前必须审查所有 skip_auth_routes 条目。旧参数 --skip-auth-regex 已标记为废弃,建议迁移到 --skip-auth-route

GAP-Signature 请求签名

当前 oauth2-proxy 配置文档把 --signature-key 定义为 GAP-Signature request signature key,参数格式为 algorithm:secretkey。它不是“给所有 X-Auth-Request-* Header 自动加 HMAC”的通用开关;后端是否能验证签名,必须按实际使用的 GAP 接入协议实现并做联调。不要把它当成替代网络隔离、Ingress 清理客户端同名 Header 或后端授权校验的捷径。

--set-authorization-header=true    # 把 Bearer token 透传到后端;仅在确有需要时开启
--set-xauthrequest=true            # 注入 X-Auth-Request-* 响应 Header
--signature-key=sha256:<secret>    # GAP-Signature 密钥;算法和格式按版本文档确认

--set-xauthrequest 的职责只是生成认证响应头;--pass-access-token 会额外产生 X-Auth-Request-Access-Token。如果后端不需要 Token,就不要打开透传,减少凭据传播面。后端仍必须只接受来自受信任 Ingress 的请求,并独立验证自己消费的 Token 的 issaud、签名、过期时间和权限。

版本演进与安全更新(v7.9 → v7.15.4)

oauth2-proxy 从 v7.8.2(2025-03)到 v7.15.4(2026-08)经历了多个版本,包含若干安全审计修复和配置行为变更。下表按版本列出影响生产部署的关键变更,升级前应逐条核对。

版本发布日期关键变更生产影响
v7.9.02025-04修复 Keycloak OIDC Provider 从 access token 提取 role 的逻辑;支持 JWT 编码的 profile claims使用 --allowed-role 控制 Keycloak 访问的部署应验证角色提取行为
v7.10.02025-07GitHub/Gitea Provider 支持多个 org;Redis 空链接列表返回错误多 org GitHub 集成可用;Redis 配置异常不再静默
v7.11.02025-11安全修复skip_auth_routes 正则从匹配完整 URI 改为只匹配路径;修复 Alpha Config $ 双重转义必须审查白名单规则——依赖查询参数匹配的 skip_auth_routes 会失效或行为改变
v7.13.02026-02OIDC Provider 刷新会话时改用 access_token 而非 id_token 验证;Header 名称归一化(X-Forwarded-ForX_Forwarded_For 等价处理)若 IDP 刷新时不签发新 id_token(符合 OIDC 规范),会话续期不再失败;自定义 Header 剥离规则需检查大小写兼容
v7.14.02026-01Alpha Config YAML 结构变更:injectRequestHeadersvalues 必须显式嵌套为 claimSource/secretSource/valueSource使用 Alpha Config 的部署升级前必须迁移 Header 注入配置格式
v7.15.02026-03支持 OIDC JWT signing algorithm 配置;CSRF Cookie 使用 CSRFExpire 而非 Expire 校验;新增 --config-test 标志;支持从 ID Token/UserInfo 注入任意 claim 到 Sessioncookie-csrf-expire 配置需检查;--config-test 可用于 CI/CD 配置预检
v7.15.22026-04安全审计修复:health check User-Agent 认证绕过(Critical)、X-Forwarded-Uri 伪造认证绕过(Critical)、fragment 路由评估(High)、malformed multi-@ email 验证绕过(Moderate);新增 --trusted-proxy-ip 参数必须升级——旧版本存在多个认证绕过漏洞;--trusted-proxy-ip 是新的生产加固必选项
v7.15.32026-06Go 1.26 升级、依赖更新、多个 CVE 修复安全补丁版本
v7.15.42026-08升级 Go 与依赖,并修复多个已公开 CVE应优先替换 v7.15.3;先在预发验证 Provider、Cookie 和代理头行为

--trusted-proxy-ip:转发头信任边界

v7.15.2 引入 --trusted-proxy-ip,用于显式指定允许发送 X-Forwarded-* 头的反向代理 IP 或 CIDR 范围。未设置时 oauth2-proxy 保留向后兼容行为(信任所有来源 0.0.0.0/0),并在启动时记录告警。

--reverse-proxy=true
--trusted-proxy-ip=10.0.0.0/8     # 只信任集群内网代理
--trusted-proxy-ip=172.16.0.0/12  # 可多次指定

生产部署中如果客户端能绕过 Ingress 直连 oauth2-proxy Service,未设置此参数意味着攻击者可以伪造 X-Forwarded-Uri 等头绕过认证——这正是 v7.15.2 修复的 Critical 漏洞( GHSA-7x63-xv5r-3p2x)的利用路径。

v7.15.0 之前,CSRF Cookie 的过期校验错误地使用了 Expire(Session Cookie 有效期)而非 CSRFExpire。如果你的 CSRF Cookie 寿命与 Session Cookie 不同(例如 Session 8h、CSRF 1h),升级后 CSRF 校验会按 --cookie-csrf-expire 独立判断。未显式设置 --cookie-csrf-expire 时使用默认值 12h。

--config-test:配置预检(v7.15.0)

v7.15.0 新增 --config-test 标志,在 CI/CD 流水线中可先验证配置再部署:

oauth2-proxy --config=/etc/oauth2-proxy.cfg --config-test && echo "config OK"

退出码 0 表示配置有效,非 0 表示有错误。适合在 Helm hook 或 pre-deploy 阶段拦截配置问题。

与替代方案对比

方案定位适合不适合
oauth2-proxy轻量 OAuth2 反向代理内部工具统一登录,Ingress 层拦截,快速集成需要细粒度 RBAC/策略引擎的场景
Pomerium企业级零信任接入代理需要细粒度策略、设备信任、多 IDP 联合小团队快速上手(配置复杂度高)
Traefik ForwardAuth内置中间件已用 Traefik 的 K8s 集群需要单独部署一个 ForwardAuth 后端(可以是 oauth2-proxy)
Nginx auth_request内置模块已用 Nginx Ingress 的 K8s 集群需要单独部署一个 auth 后端(可以是 oauth2-proxy)
Nginx ngx_http_auth_jwt_moduleJWT 本地校验API 网关,高性能 JWT 验证需要 OAuth2 回调流程的场景(模块只管校验,不管登录)
Ory OathkeeperAPI 优先的认证鉴权网关微服务架构,需要 Zero Trust 策略引擎

关键区别:Traefik ForwardAuth 和 Nginx auth_request协议(不是产品),它们本身不做认证,需要指向一个认证后端——而 oauth2-proxy 正好是最常用的认证后端实现。

生产上线检查清单

  • --cookie-secret 已配置,值为解码后 16/24/32 字节的 URL-safe Base64,所有实例一致。
  • 配置来源已核对:命令行参数 > 环境变量 > 配置文件,未被 Helm 或 Secret 中的同名值覆盖。
  • --cookie-secure=true(HTTPS 环境)。
  • --cookie-httponly=true,未关闭。
  • --cookie-samesite 已根据部署拓扑设置(子域共享 Cookie 时注意)。
  • Keycloak Client 已配置 Audience mapper,Token aud 包含 oauth2-proxy Client ID。
  • --oidc-issuer-url 与实际 Token iss 完全一致(无尾斜杠,无 /auth 差异)。
  • groups/roles claim 已映射,--allowed-group 按应用粒度生效。
  • --skip-auth-route 已配置健康检查和白名单路径。
  • --skip-auth-route 规则不依赖查询参数匹配(v7.11.0 修复后只匹配路径)。
  • 启用 --reverse-proxy 时已设置 --trusted-proxy-ip 为受控代理 IP/CIDR(v7.15.2+ 生产必选项)。
  • /oauth2 路径已路由到 oauth2-proxy(不是只配了 /oauth2/auth)。
  • 若启用 --signature-key,已按实际 GAP-Signature 协议完成后端验签联调;没有该需求时不启用。
  • 镜像版本不低于 v7.15.4(截至 2026-08-24 的最新 release;v7.15.2 及后续版本修复了多个认证绕过/安全问题)。
  • 若使用 Alpha Config,Header 注入配置已迁移到 v7.14.0+ 的嵌套格式。
  • 回滚方案:删除 Ingress 上的 auth-url/auth-signin 注解或 Traefik ForwardAuth middleware 引用,保留 oauth2-proxy 实例以便事后复盘。

IAM FAQ

oauth2-proxy 是 IAM 还是应用网关?

它是 IAM 链路中的入口认证代理,不是完整 IAM 平台。Keycloak、Dex 等 Provider 负责身份认证和 Token 签发;oauth2-proxy 负责浏览器会话、入口放行和认证结果传递。用户、组、MFA、生命周期和审计策略不能只靠 oauth2-proxy 管理。

X-Auth-Request-User 能直接当授权依据吗?

不能。它是认证代理输出的 Header,不是后端资源授权证明。Ingress 必须清理客户端伪造的同名 Header,后端还要根据自身的 Token、scope、role 或资源关系做授权;高风险操作还要单独判断认证强度和实时会话状态。

多副本 oauth2-proxy 是否必须使用 Redis?

不必须。默认 Cookie Session Store 下,多副本共享同一个 --cookie-secret 即可读取会话。只有 Cookie 过大、需要服务端集中撤销或不希望 Token 留在 Cookie 中时,再引入 Redis;引入后要增加 TLS、凭据、容量、监控、故障和回滚验证。

升级到 v7.15.4 后必须做什么?

三件事:设置 --trusted-proxy-ip 限制可发送 X-Forwarded-* 头的代理来源,审查所有 --skip-auth-route 规则确认不依赖查询参数匹配,并在预发验证 v7.15.4 的 OIDC 登录、会话刷新和回调。v7.15.2 修复了多个 Critical 认证绕过漏洞;v7.15.4 的发行说明又列出多项依赖与安全修复,因此不应继续把 v7.15.3 作为新部署基线。

参考与延伸阅读