Zero Trust Access:给内部工具加一层登录,不改代码
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 name | internal-admin | 只用于控制台显示 |
| Session duration | 24 hours | 到期后重新验证 |
| Public hostname | admin.example.com | 子域 + 路径,路径可留空表示整站 |
| Path | 留空 或 /app | 只保护某个前缀时填 |
| Policy | Allow + 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 验签代码片段只写到取值与验签思路,未部署运行。