oauth2-proxy 内置端点(Endpoints)完全指南:认证路由、登出流程与授权参数解析
OAuth2 Proxy 不仅是一个透明的反向代理,它还对外暴露了一组由代理自身直接响应的内置端点,用于承载登录页、OAuth 启动与回调、注销、健康检查、指标暴露等关键能力。本文以 oauth2-proxy 7.14.x 版本的官方文档为核心,结合仓库源码逐一拆解每个端点的路由注册方式、HTTP 语义与实现原理,帮助你掌握如何将 /oauth2/* 端点正确嵌入 Nginx、Kubernetes 等架构,并安全地使用 allowed_groups、allowed_email_domains、allowed_emails 等请求级授权参数。
端点全景:OAuth2 Proxy 直接响应的内置路由
OAuth2 Proxy 会直接响应下表列出的端点;除这些端点外的所有路径,在认证通过后都会被代理到上游(upstream)服务。所有 /oauth2 前缀路径的实际根路径可以通过 --proxy-prefix 配置项修改(默认值为 /oauth2),例如设置为 /auth 后,/oauth2/sign_in 将变为 /auth/sign_in。
| 端点 | 作用 | 说明 |
|---|---|---|
/ |
代理端点 | 未认证/未授权时返回对应 40x 错误,通过后把请求转发到上游 |
/robots.txt |
爬虫协议 | 返回 200 OK,并禁止所有 User-agent 抓取所有路径 |
/ping |
存活探针 | 返回 200 OK,用于基础健康检查 |
/ready |
就绪探针 | 当所有底层连接(如 Redis 会话存储)就绪时返回 200 OK |
/metrics |
Prometheus 指标 | 由 --metrics-address 指定监听地址,默认关闭 |
/oauth2/sign_in |
登录页 | 兼作登出页(渲染时会清除 Cookie) |
/oauth2/sign_out |
注销 | 清除会话 Cookie |
/oauth2/start |
启动 OAuth 流程 | 302 重定向到身份提供商 |
/oauth2/callback |
OAuth 回调 | OAuth 应用需将其配置为回调地址 |
/oauth2/userinfo |
用户信息 | 以 JSON 返回会话中的用户信息 |
/oauth2/auth |
鉴权端点 | 只返回 202/401/403,供 Nginx auth_request 指令使用 |
/oauth2/static/* |
静态资源 | sign_in 与 error 页面依赖的样式等 |
这些端点在源码中均以常量形式集中定义,位于 oauthproxy.go 的路径常量区:
robotsPath = "/robots.txt"
signInPath = "/sign_in"
signOutPath = "/sign_out"
oauthStartPath = "/start"
oauthCallbackPath = "/callback"
authOnlyPath = "/auth"
userInfoPath = "/userinfo"
staticPathPrefix = "/static/"
路由的实际装配发生在 buildServeMux 与 buildProxySubrouter 两个函数中(见 oauthproxy.go#L318-L356):所有请求先经过 preAuthChain(强制 HTTPS、健康检查、日志、指标采集),/robots.txt 与 /oauth2/auth 单独注册,其余 /oauth2/* 端点注册在 proxy 前缀子路由器下,最后以 r.PathPrefix("/") 兜底接管所有未被捕获的路径并交给 Proxy 处理。值得注意的一个实现细节是:/oauth2/auth 被刻意单独注册,目的是避免给它附加 no-cache 响应头,这样用户可以在 Nginx 侧对鉴权响应做短期缓存,降低多个请求同时刷新会话的概率。
根路径 /:代理与认证入口
/ 是代理的核心端点。当请求未命中任何内置端点时,会进入 Proxy 处理逻辑(见 oauthproxy.go#L1039-L1078),其行为分为三种情况:
- 会话有效且通过授权检查:注入认证相关请求头后,将请求交给
upstreamProxy转发到上游服务,返回上游响应; - 未认证(
ErrNeedsLogin):若启用了--force-json-errors,或请求来自 AJAX(X-Requested-With等特征)、或命中 API 路径,则直接返回 401 JSON;否则若配置了--skip-provider-button会直接启动 OAuth 流程(302 重定向),默认情况下则渲染登录页; - 授权检查不通过:返回 403 Forbidden。
文档对 / 的行为概括为"未认证返回 401、不在允许名单返回 403",但从前述源码可以看出,未认证时的实际表现取决于 JSON 错误开关与 AJAX 判定,在浏览器普通请求场景下通常是跳转登录页而非直接返回 401,这一点在集成时需要注意。
/ 端点支持通过请求查询参数进行请求级授权覆盖:
allowed_groups:允许的组,逗号分隔列表;allowed_email_domains:允许的邮箱域名,逗号分隔列表;allowed_emails:允许的邮箱地址,逗号分隔列表。
这些参数的解析与校验实现在 oauthproxy.go#L1178-L1278 的 authOnlyAuthorize 及三个 check* 函数中。解析时通过 extractAllowedEntities 把逗号分隔的字符串拆成 map[string]struct{},以常数时间完成成员判断;checkAllowedEmailDomains 对邮箱域名做子域/端口匹配校验,checkAllowedGroups 逐项比对会话中的组,checkAllowedEmails 做精确邮箱匹配。也就是说,同样的授权参数同时作用于 /oauth2/auth 与 / 两个端点。
/robots.txt:爬虫禁止协议
/robots.txt 返回 200 OK,内容为标准的禁止抓取声明,禁止所有 User-agent 访问所有路径。该内容由静态页面处理器写入,见 static_pages.go#L28-L46,实际内容定义在 robots.txt 中:
User-agent: *
Disallow: /
这一设计意图很直观:认证代理本身不提供任何公开内容,搜索引擎的爬虫不应该被引导到需要登录的受保护应用上。
/ping 与 /ready:健康检查双通道
/ping:返回 200 OK,用于基础存活检查(liveness),只确认进程活着;/ready:返回 200 OK 的前提是所有底层连接可用(例如 Redis 会话存储、SQL 会话持久化已连接),用于深度就绪检查(readiness)。
两个路径均可自定义:--ping-path 与 --ready-path 的默认值分别为 /ping 与 /ready(见 options.go#L103-L106)。健康检查中间件注册在 preAuthChain 中,先于请求日志中间件执行,因此健康检查请求默认不会污染日志(--silence-ping-logging 相关行为可在 oauthproxy.go#L372-L394 中看到)。此外,若设置 --gcp-health-checks,还会额外响应 Google Cloud 的 /liveness_check 与 /readiness_check 路径(该选项已标记为弃用,官方建议改用 ping 路径并将用户代理设为 GoogleHC/1.0 以保持原有行为)。
在 Kubernetes 部署中,典型的用法是把 /ping 挂到 livenessProbe、把 /ready 挂到 readinessProbe。
/metrics:Prometheus 指标端点
/metrics 端点用于让 Prometheus 抓取指标,但默认是关闭的——它不会随主服务一起监听。需要通过 --metrics-address 指定独立的监听地址(如 :9100),该配置项定义于 legacy_options.go#L475,默认值为空字符串。从 oauthproxy.go#L304-L314 可以看出,指标由 middleware.DefaultMetricsHandler 处理,并被构建为一个独立的 metrics 服务,与主应用服务一起组成服务组分别监听,因此把指标端口与业务端口分离是默认的安全姿势。
/oauth2/sign_in:登录页(兼作登出页)
/oauth2/sign_in 渲染登录页面。文档特别强调它"同时兼作登出页":渲染登录页的过程中会主动清除会话 Cookie(SignInPage 中调用 ClearSessionCookie),因此在已登录状态下访问该页面,等价于把用户"登出"并重新引导登录。
页面上的"Sign in with ..."按钮指向 SignIn 处理器(见 oauthproxy.go#L689-L716),其行为逻辑是:
- 若是 POST 请求且配置了 htpasswd 基础认证(
--htpasswd-file),先尝试ManualSignIn手动登录,成功则直接建立会话并 302 跳转回原目标; - 否则若启用了
--skip-provider-button(跳过登录页按钮),直接调用OAuthStart进入 OAuth 流程; - 默认情况下渲染
SignInPage,其中顶部文案由--banner或邮箱域名自动生成(如 "Authenticate using example.com"),该文案构建逻辑见 oauthproxy.go#L456-L472。
/oauth2/sign_out:注销与登出重定向
/oauth2/sign_out 用于清除 oauth2-proxy 自己的会话 Cookie。注意:它不会让用户在身份提供商(IdP)侧登出——用户仍然保持着与 IdP 的登录态,下次访问应用时可能被自动重新登录。因此,通常需要在清除会话后,把用户重定向到 IdP 的登出页面完成真正意义上的登出。
通过 rd 查询参数指定重定向目标
重定向到 IdP 登出页需要借助 rd 查询参数,必须对 URL 做百分号编码,例如:
/oauth2/sign_out?rd=https%3A%2F%2Fmy-oidc-provider.example.com%2Fsign_out_page
通过 X-Auth-Request-Redirect 请求头指定
也可以把重定向地址放在 X-Auth-Request-Redirect 请求头中:
GET /oauth2/sign_out HTTP/1.1
X-Auth-Request-Redirect: https://my-oidc-provider/sign_out_page
...
对于 OIDC 提供商,sign_out_page 应当取自提供商元数据中的 end_session_endpoint(前提是提供商支持 OIDC Session Management 与 Discovery)。
重定向域必须加入白名单
务必注意:rd 指向的域(上例中的 my-oidc-provider.example.com)必须被加入 --whitelist-domain 配置项,否则重定向会被忽略。白名单里填写的是域名与端口,而不是完整 URL,例如 localhost:8081 而非 http://localhost:8081。关于该配置的详细规则(见 配置总览):以 . 或 *. 前缀可放行该域的所有子域;默认只允许空端口(即 URL 协议对应的默认端口,浏览器通常会省略它);如需精确放行某个端口写作 example.com:8080,如需放行任意端口写作 example.com:*。
从源码看(oauthproxy.go#L756-L786),SignOut 的完整流程是:先通过 GetRedirect 取得 rd 参数或 X-Auth-Request-Redirect 头中的重定向目标;然后调用 backendLogout 通知提供商后端登出(若配置了 BackendLogoutURL,会替换其中的 {id_token} 占位符并发出请求,见 oauthproxy.go#L788-L817);随后清除会话 Cookie,最后 302 跳转。在更高版本的当前主线文档(docs/docs/features/endpoints.md)中,rd 与 X-Auth-Request-Redirect 还支持 {id_token} 占位符注入,例如拼接出 ?id_token_hint={id_token}&post_logout_redirect_uri=... 形式的 IdP 登出 URL,7.14.x 版本可参考该演进方向自行评估升级。
/oauth2/start:启动 OAuth 流程
/oauth2/start 负责启动一次 OAuth 授权码流程:生成必要的安全参数后,302 重定向到身份提供商的登录地址。从 oauthproxy.go#L819-L881 的实现可以看到它在重定向前完成的准备工作:
- PKCE 参数:若提供商配置了
CodeChallengeMethod(如S256),会生成 96 字符的 code verifier 并计算 code challenge,随登录请求附带code_challenge与code_challenge_method; - CSRF Cookie:基于 code verifier 与 Cookie 选项生成 CSRF 随机数并写入 Cookie;
- state 编码:通过
encodeState把 CSRF nonce 与原始应用重定向地址打包(默认做 base64url 编码),防止跨站请求伪造并保留登录后的回跳目标; - 登录 URL 构造:调用
provider.GetLoginURL生成 IdP 登录页地址,同时允许以查询参数覆盖默认的登录 URL 参数(如approval_prompt等)。
/oauth2/callback:OAuth 回调终点
/oauth2/callback 是 OAuth 授权码流程的收尾环节,需要在身份提供商的 OAuth 应用配置中登记为回调地址。其完整校验链(见 oauthproxy.go#L883-L977)为:
- 解析表单,若 IdP 返回
error参数则渲染 403 错误页; - 解码
state,还原 CSRF nonce 与应用重定向地址; - 依据 nonce 计算 Cookie 名并加载 CSRF Cookie,缺失时记录详细日志并拒绝(这对应社区常见的"missing CSRF cookie"问题);
- 用授权码与 code verifier 调用
provider.Redeem换取会话(oauthproxy.go#L979-L1000); - 丰富会话(补全邮箱、附加声明等),核对 CSRF state 是否匹配,校验会话有效性;
- 校验应用重定向地址是否在
--whitelist-domain白名单内(无效则回退到/); - 执行
provider.Authorize与全局Validator授权检查,通过则写入会话 Cookie 并 302 跳回应用,否则返回 403。
/oauth2/userinfo:以 JSON 返回用户信息
/oauth2/userinfo 在已认证时以 application/json 返回会话中的用户信息,未认证则返回 401。从 oauthproxy.go#L718-L754 的实现可以看出响应体包含以下字段:
{
"user": "alice",
"email": "alice@example.com",
"groups": ["group1", "group2"],
"preferredUsername": "alice",
"additionalClaims": {}
}
其中 groups、preferredUsername、additionalClaims 在会话中不存在时会被省略(omitempty);若会话为空则返回 {}。这个端点适合作为前端或微服务获取当前登录用户身份的标准入口。
/oauth2/auth:Nginx auth_request 专用鉴权端点
/oauth2/auth 是一个"只回答是与否"的极简鉴权端点,专门用于配合 Nginx 的 auth_request 指令做子请求鉴权,集成方式见 Nginx 集成文档。文档描述其只返回 202 Accepted 或 401 Unauthorized,但从 oauthproxy.go#L1016-L1037 的 AuthOnly 实现可以看到完整的三种响应语义:
- 202 Accepted:已认证且通过
authOnlyAuthorize授权检查,同时在响应中注入X-Auth-Request-*等认证头供上游使用; - 401 Unauthorized:会话缺失(
getAuthenticatedSession失败); - 403 Forbidden:会话存在但未通过
allowed_groups/allowed_email_domains/allowed_emails等请求级授权检查——源码注释特别说明,在子请求架构下返回 403 是为了避免无限重定向循环。
它支持与 / 完全相同的三个查询参数:allowed_groups、allowed_email_domains、allowed_emails。这使 Nginx 可以针对不同 location 传递不同的授权约束,实现同一代理下的差异化访问控制。
/oauth2/static/*:页面静态资源
/oauth2/static/* 提供 sign_in 页面与 error 页面所依赖的样式表和字体等静态资源,内容通过 Go embed 内嵌在二进制中(见 oauthproxy.go#L70-L71 与 pkg/app/pagewriter 下的默认模板),由 http.FileServer 配合 StripPrefix 提供服务,无需额外部署静态目录。
配置与运维要点小结
- 修改前缀:
--proxy-prefix(默认/oauth2)会整体移动所有/oauth2/*端点,例如--proxy-prefix=/auth后sign_in、callback、auth等路径的前缀随之变化,需同步更新 IdP 侧登记的回调地址与反向代理规则; - 健康检查:优先把
/ping与/ready分别接入容器编排的 liveness 与 readiness 探针,二者路径均可通过--ping-path、--ready-path调整; - 指标安全:
/metrics默认不监听,启用--metrics-address时应绑定内网或独立端口,避免将指标暴露到公网; - 登出闭环:
/oauth2/sign_out只清除本代理会话,务必通过rd或X-Auth-Request-Redirect配合--whitelist-domain把用户送回 IdP 登出页,才能形成完整的单点登出闭环; - 请求级授权:
/oauth2/auth与/均支持allowed_groups、allowed_email_domains、allowed_emails查询参数,可在不改全局配置的前提下按路径或按请求做细粒度放行。
以上所有端点行为均可对照仓库源码 oauthproxy.go 中的路由注册与各处理器实现逐行验证,配合 配置总览 可构建出完整的认证代理集成方案。
