KernelRanch
返回文章列表

Zero Trust Access:给内部工具加一层登录,不改代码

2026-11-10 #Cloudflare

Access 解决的是一个很具体的问题:某个页面只该给团队里的几个人看,但你又不想为它写登录系统。做法是把域名的某条路径挂到一个 Access 应用上,请求到达边缘时先过身份验证,通过之后才回源。应用本身不需要改一行代码,也拿不到未登录的请求。

免费计划给 50 个席位,内置一次性验证码(One-time PIN)作为登录方式,不需要接第三方 IdP 就能跑起来。这篇从建团队域开始,到一个受保护的 Worker 站点跑通,最后说清哪些情况下 Access 不适用。

前置:域名已接入并处于 Active 状态,做法见 49-add-domain-to-cloudflare.md。Access 只能保护在你账号下的、走代理的主机名。

1. 免费档拿到什么

项目免费档说明
席位50 个用户团队规模在此以内不用付费
登录方式One-time PIN 内置,另可接 Google / GitHub / SAML / OIDC一次性验证码零配置
应用类型自建应用、SaaS 应用、SSH、VNC、Dashboard自建应用最常用
策略动作Allow、Block、Bypass、Service Auth按需组合
会话时长可配按应用设置,到期重新验证
日志基础审计长时间留存与外送需要付费能力,方法见 85-logpush-logs.md

席位按"过去一段时间内有登录行为的用户"计算,超过 50 人会提示升级。判断标准以控制台显示为准。

2. 建团队域

登录 Cloudflare 控制台,左侧进 Zero Trust(独立入口 one.dash.cloudflare.com)。

第一次进入会让你设一个团队名,它会变成 <team>.cloudflareaccess.com,后面登录页、证书、JWT 校验都依赖这个域名。名字定了不好改,用公司或项目简称。

然后到 Settings → Authentication → Login methods,确认 One-time PIN 是开启状态。这是默认就有的,不需要配 SMTP,验证码由 Cloudflare 发。想用企业 SSO 再在这里加 Google 或 GitHub OAuth,需要自己申请 OAuth 应用并填 Client ID / Secret。

3. 建应用与策略

Access → Applications → Add an application → Self-hosted。

要填的字段不多:

字段示例说明
Application nameinternal-admin只用于控制台显示
Session duration24 hours到期后重新验证
Public hostnameadmin.example.com子域 + 路径,路径可留空表示整站
Path留空 或 /app只保护某个前缀时填
PolicyAllow + Emails允许哪些人进

策略是 Access 的核心。一条 Allow 策略里可以叠加条件:邮箱列表、邮箱域名(@yourcompany.com)、IdP 分组、国家、IP 段、设备状态。条件之间是"与"的关系,多个策略之间是"或"的关系。

常用的两条:

Allow  if  email domain is yourcompany.com
Allow  if  email in {"alice@example.com", "bob@example.com"}

第一条覆盖整个公司域,第二条给外部顾问开单个邮箱。两条都挂在同一个应用上,任一命中即放行。

再留一条 Block 策略把明显不该来的来源挡掉,放在 Allow 之前:

Block  if  ip.geoip.country ne "CN" and ip.geoip.country ne "SG"

地理条件容易误伤出差与移动网络,只在内部工具上使用,别用在客户可见的站点。

Access 与 WAF 是两套东西:Access 管"这个人能不能进",WAF 管"这个请求像不像攻击"(77-waf-basics-free-rules.md)。有人以为把后台挂到 Access 后面就不用管 WAF 了,实际上绕过登录页去打接口的流量仍然需要边缘侧的规则。

4. 验证登录流程

用无痕窗口访问受保护的域名,应该看到 Cloudflare 的登录页而不是应用内容。输入邮箱,收到六位验证码,填进去,通过后跳回原地址,浏览器里多一个 CF_Authorization cookie。

这一步能验证三件事:应用绑定的主机名是否正确、策略是否匹配到你的邮箱、验证码邮件是否可达。失败时常见的两种表现是"一直跳登录页"(cookie 被浏览器策略拦掉)和"提示无权限"(策略没匹配上,检查邮箱域名拼写)。

5. 机器访问:Service Token

浏览器登录解决了人,脚本与 CI 要另开一条路。Access → Service Auth 建一个 token,拿到 Client ID 与 Client Secret,请求时带上两个头:

curl -H "CF-Access-Client-Id: <id>.access" \
     -H "CF-Access-Client-Secret: <secret>" \
     https://admin.example.com/api/health

对应的策略动作选 Service Auth,条件里选刚建的 token。别用 Bypass 给自动化开口子——Bypass 等于完全跳过验证,任何知道 URL 的人都能进。

6. 在 Worker 里读身份

Access 验证通过后,会在请求里带上 Cf-Access-Jwt-Assertion 头,里面是签名的 JWT。Worker 可以据此做应用内的权限判断,比如区分管理员与只读用户:

const jwt = request.headers.get("Cf-Access-Jwt-Assertion");
if (!jwt) return new Response("unauthorized", { status: 401 });
// 生产环境要验签:从 https://<team>.cloudflareaccess.com/cdn-cgi/access/certs 取公钥
// 并校验 aud 等于该应用的 Audience Tag

只检查头是否存在是不够的,攻击者可以伪造同名头——前提是请求能绕过 Access 直接到 Worker。所以 Worker 的入口域名要保证只经由 Access 保护的路径进入,验签逻辑按官方示例补上。

常见问题

workers.dev 域名能加 Access 吗?

不能。Access 应用绑定的主机名要属于你账号下的某个 Zone,*.workers.dev 是 Cloudflare 的域名。要用 Access,先给 Worker 配自定义域,见 55-workers-routes-custom-domain.md。

50 个席位怎么算?

按有登录行为的用户计数,具体口径以控制台账单页为准。团队超过 50 人时,常见做法是只用 Access 保护真正敏感的几个应用,其余靠应用自身的账号体系。

Access 和 Tunnel 是什么关系?

Access 管"谁能进",Tunnel 管"怎么进"。要暴露家里或机房的内网服务,先用 Tunnel 把服务接到边缘,再用 Access 加身份验证。只做 Tunnel 不加 Access,等于把内网服务公开到互联网。

小结

Access 把"写登录系统"变成"配一条策略"。免费档 50 席位、内置验证码,半小时内能把后台、预览环境、内部报表挡在登录页后面,且不改应用代码。

它保护的是主机名,不是"应用"这个概念。所以真正的前置工作是域名与代理状态的规划:哪些子域公开、哪些子域只对内部开放。这个划分清楚了,Access 的配置只是几步点击。

数据来源与检索时间

检索时间 2026-11-10。

来源URL对应数据
Cloudflare 定价页https://www.cloudflare.com/plans/Zero Trust 免费档 50 席位
Zero Trust 文档首页https://developers.cloudflare.com/cloudflare-one/应用类型、策略动作、身份验证方式的产品结构
Workers 路由https://developers.cloudflare.com/workers/configuration/routing/routes/自定义域与 *.workers.dev 的差别(Access 只能绑定自有 Zone 的主机名)

核验说明

  • 页面为本机抓取(HTTP 200),席位数字取自定价页描述。
  • 未执行:无 Zone 权限,Zero Trust 团队域未创建、应用与策略未配置、登录流程与 Service Token 均未实测;控制台路径按文档结构描述。
  • 未验证:50 席位的具体计数口径(月活还是注册用户)未在账号确认;JWT 验签代码片段只写到取值与验签思路,未部署运行。