你可能已经在处理这些问题了。应用登录正常,用户可以用 Google、Microsoft 或邮箱登录,API 也能接收 token。但核心问题随之而来:这个用户能看到另一个账户的发票吗?桌面端应用应该在本地缓存 access token 吗?在 Capacitor 构建中,如何在不跨 webview 和原生层泄露状态的前提下处理用户同意?
这时,应用授权不再只是一个勾选项,它开始直接影响产品信任度、事件响应以及 App Store 的审核结果。在跨平台应用中,尤其是使用 Capacitor 和 Electron 时,难点不在于理解授权的概念,而在于如何在浏览器假设不再成立的地方实现它——安全存储在各平台上的行为不同,而客户端上的捷径会带来服务端风险。
目录
应用授权的真正含义
用户安装你的 App,点击“使用 Google 继续”并成功登录后,会看到一个同意屏幕,询问 App 能否读取联系人或日历数据。这一刻同时体现了访问控制的两面:登录确认身份,同意屏幕则界定身份明确后 App 可以做什么。
这种区别仍会让团队踩坑。 身份验证 证明用户是谁。 授权 决定该用户、会话或 App 能访问什么。ID 让你进入大楼,密钥则决定哪些门会打开。
在 App 开发中,这种差异很关键,因为团队往往把登录流程保护得很好,却对后续环节设计不足。他们可能过度信任 Token、跳过服务端权限校验,或让客户端决定本应放在策略里的访问规则。于是,“用户已登录”就悄然变成了“用户能访问太多东西”。
授权正是让信任落地的关键。用户不仅关心你的 App 是否知道他们是谁,更关心它是否只访问他们批准的内容。
需要理清三个角色:
- 用户 负责登录,并可能授予同意。
- 应用程序 代表用户请求访问权限。
- 资源所有者或 API 负责保护数据并执行该决定。
在实践中,应用授权也与 MFA 等更强的访问控制存在交叉。截至 2023 年 1 月, 全球约 66% 的用户在使用 MFA,以及 在 2024 年 JumpCloud 的一项调查中,超过 1,000 名受访的中小企业 IT 专业人士中,有 83% 要求访问所有公司资源时必须使用 MFA 数据来自 JumpCloud 的 MFA 统计汇总。这并不能取代授权,但它确实提高了最初请求访问权限的门槛。
如果你的团队正在梳理角色、作用域和委托访问,这篇关于 RBAC 如何保障 Capacitor 应用中的 OTA 更新安全 的文章,可作为本文所讨论实现方案的有益补充。
授权的核心构建模块
一旦你不再把授权当成 token 里的“魔法”,它就会简单得多。更好的心智模型是酒店。
酒店房卡是一个很好的心智模型
客人走到前台出示证件。酒店验证身份、创建入住记录并发放房卡。每次开门时,房卡并不证明客人是谁;它携带的是在限定时间内访问特定区域的权限。
你的应用运作方式也是如此。

重点在于,房卡本身并不是策略,它反映的是策略。门仍然需要一个系统来检查这张房卡是否应该打开那把特定的锁。在软件中,这就是你的 API 网关、后端中间件、策略引擎或服务级授权层。
实际系统中真正重要的术语
Principal(主体)
请求访问的执行者。通常是用户,也可以是设备、后台任务或服务账号。
Resource(资源)
受保护的对象。例如项目、发票、管理路由、文件、API endpoint 或数据库中的单条记录。
范围
请求的操作集合。读取个人资料、上传文件、管理账单。Scope 应当尽量窄且易懂。
同意
用户对请求访问权限的授权。优秀的授权界面应让请求清晰易懂;糟糕的界面则会索取所有权限。
Access token(访问令牌)
授权成功后,客户端向资源服务器出示的凭证。应将其视为敏感数据处理。
许多实现错误都来自把所有概念压缩成一个假设:“用户有 token,就放行。”这在生产环境行不通。token 可能有效,但对当前操作、租户、环境或资源来说仍是错的。
对于移动端和桌面端团队来说,Token 处理需要格外关注,因为不管你是否愿意,存储都是授权系统的一部分。如果客户端存储访问凭证太随意,策略设计得再好,之后也救不了你。这篇关于 移动端开发者的安全 Token 存储 的指南,值得你在发布前仔细研读。
一条长期有效的原则很简单:在脑子里和 code 中,都要把身份验证、用户同意、Token 签发和服务端强制执行分开。把它们混在一起的团队,最后往往会在错误的层级调试权限 bug。
常见的授权模型与协议
当团队说“我们正在使用 OAuth”时,往往同时指代好几件不同的事。这也是混淆的部分原因。协议和授权模型解决的是不同的问题。
协议负责处理通信
OAuth 2.0 主要讲的是委托授权。它定义了应用如何在不直接处理用户密码的情况下,请求并获取代表用户行事的权限。
OpenID Connect(或称 OIDC)构建在 OAuth 2.0 之上并增加了身份信息。从实际来看,OAuth 回答的是“这个应用能做什么”,而 OIDC 帮助回答“谁登录了”。
这种区别在 Capacitor 和 Electron 应用里很重要,因为很多 bug 都源于:本该用 access token 的地方用了 ID token,或者以为登录成功就意味着 API 应该授权所有下游操作。其实不该如此。
如果你要把它接入混合应用,一份分步的 面向 Capacitor 应用的 OAuth2 实现指南 正是那种能避免许多本可避免的流程错误的资源。
模型负责决策逻辑
在你自己的系统里,你仍然需要规则来判断是否应授予访问权限。这就是 RBAC、 以及 ABAC 发挥作用的地方。
基于角色的访问控制(RBAC) 将权限映射到 admin、editor、support agent 或 viewer 等角色。它之所以常见,是因为它易于理解、可审计且相对稳定。根据 BrightSec 关于安全身份验证与授权的讨论, RBAC 是实施细粒度权限的行业标准机制,且其中引用的证据表明,采用层级角色结构实施 RBAC 并定期进行权限审计 可将企业环境中的安全事件减少多达 40%.
基于属性的访问控制(ABAC) 使用属性而非仅靠角色来做决策。这些属性可以包括部门、设备状态、记录所有权、账户等级、地理位置、请求时间,或会话是否通过了 MFA。ABAC 表达力更强,但如果策略文档不完善,也更容易变得不透明。
实战规则: 当产品权限稳定、可读性好时,先从 RBAC 开始。在上下文确实会改变决策的地方再引入 ABAC。
RBAC 与 ABAC 快速对比
| 标准 | 基于角色的访问控制(RBAC) | 基于属性的访问控制(ABAC) |
|---|---|---|
| 核心理念 | 基于角色授予访问权限 | 通过评估属性授予访问权限 |
| 最合适 | 内部工具、dashboard、管理面板 | 多租户应用、受监管工作流、上下文相关访问 |
| 易于推理 | 更便于团队和审计人员理解 | 更灵活,但更难调试 |
| 变更管理 | 添加或修改角色 | 调整策略和属性规则 |
| 常见的故障模式 | 角色泛滥 | 策略泛滥与隐藏的边界情况 |
| 示例 | “客服人员可以查看工单” | “客服人员可以在值班期间查看其所在区域账户的工单” |
选择最先进的模型并不会给你颁奖。更好的选择,是团队能够一致执行的方案。在大多数产品代码库中,这意味着用 RBAC 划定大范围的访问边界,并用有针对性的属性处理所有权、租户或设备状态等例外情况。
OAuth 2.0 流程详解
许多 OAuth 讲解过于抽象。在实际应用中,流程顺序至关重要,尤其是对于无法安全存储客户端密钥的公共客户端,如 Capacitor 和 Electron 应用。
用户点击登录时发生了什么
用户打开你的 Capacitor 应用并点击“使用 GitHub 登录”。应用生成 PKCE code 验证器及派生的 code 挑战码,然后通过系统浏览器或安全浏览器标签页将用户重定向至授权服务器。应用还会包含 state 参数,以验证响应属于其发起的请求。

在授权服务器端,用户按需登录并批准所请求的访问权限。随后,服务器重定向回应用,返回一个授权 code,而非可直接使用的长期凭证。你的应用将通过配置的 redirect URI 接收该 code。
随后,App 会用 code 换取 token。PKCE 在此过程中至关重要。App 会发送原始的 code 验证器以及授权 code。服务器会将其与之前的 code 挑战进行比对。如果匹配,token 交换就会成功。如果有人拦截了 code 但没有验证器,交换就会失败。
因此,PKCE 对 Native 和 Hybrid 客户端至关重要。这些 App 属于公共客户端。你应该假设攻击者可以检查 bundle、逆向 code 路径或篡改本地状态。PKCE 能缓解基于重定向的流程中最常见的风险之一。
如果你想在 code 中实现该流程之前先快速回顾一下,这里有一个简短演示:
跨平台 App 常出错的地方
协议本身很简单,但实现起来往往并不简单。
Capacitor App 通常会在以下环节出错:
- 使用嵌入式 WebView 进行登录 而不是使用系统浏览器。这可能会削弱预期的安全边界,并导致 Cookie 行为不一致。
- 丢失重定向状态 当 App 从浏览器恢复并回到原生壳时。
- 将 token 以明文存储在浏览器存储中 因为项目最初是作为 Web App 开发的,团队从未针对移动端重新考虑过存储方案。
Electron 应用面临着不同的问题。团队有时会让渲染进程处理过多的鉴权逻辑,在没有严格边界的情况下通过 IPC 暴露 token,或者将桌面应用视为受信任的环境。事实并非如此。打包后的桌面应用仍需秉持“客户端不可信”的安全思维。
刷新机制也需要经过深思熟虑的设计。access token 应当过期,会话应当能顺利恢复,且刷新逻辑不应在多个并发请求中引发竞态条件。这份 安全 token 刷新流程指南 是构建这部分时的可靠参考,能避免陷入重试循环或陈旧会话的混乱。
有一个实现习惯特别管用:把 OAuth 握手隔离在一个输入输出明确的小型 auth 模块里。别把重定向处理、token 解析和刷新逻辑散落到各个组件、hooks 和随手写的网络工具函数中。
安全威胁与核心最佳实践
在 code 审查中,授权漏洞很少显得触目惊心,它们看起来更像是图方便。这里放宽一点 scope,那里缓存一个 token,或者因为 UI 已经隐藏了按钮就漏掉服务端校验。等应用发布后,这些“捷径”就会变成攻击面。
反复出现的失败模式
移动生态给出了一个有用的警示信号。根据 DeepStrike 的移动端安全统计数据, 95% 的受测移动应用未能通过至少一项与身份验证和授权相关的 OWASP MASVS 控制项,以及 被分析的移动应用中有 85% 存在安全漏洞。你不必全盘接受安全营销的话术,但应重视其核心信号。授权错误非常普遍。

这些模式很常见:
- Token 泄露 源于不安全的存储、日志、崩溃报告或渲染器可访问的状态。
- 权限范围过宽 因为申请所有权限比随时间演进同意机制更容易。
- 客户端强制校验 应用隐藏了未授权操作,但 API 仍然接受它们。
- 重放和重定向攻击 当 state、PKCE 或重定向 URI 验证不严谨时。
- 权限漂移 在团队添加角色和例外、却没有定期审查之后。
如果你的后端没有对每个受保护操作验证授权,那你拥有的就不是 App 授权,而只是 UI 提示。
一份行之有效的实战清单
把最小权限原则当作默认做法,而不是事后的清理工作。在实际项目中,这意味着缩小每个 token 能做什么、缩小每个 token 的存放范围,并缩短每个凭据的有效期。
- 请求更窄的 scope: 仅请求用户当前正在使用的功能所需的权限。如果 App 可以推迟征求同意,就这么做。
- 在服务端强制执行: 将客户端视为不可信。按钮、路由和隐藏页面都不是安全边界。
- 使用平台安全存储: 在移动端,通过 plugin 访问原生 keychain 或 keystore,而不是使用普通的 local storage。在桌面端,让敏感内容不容易被 renderer 访问到。
- 验证 state 与重定向处理: 认证响应必须与您的应用发起的请求相匹配。
- 积极过期,谨慎刷新: 短生命周期的访问令牌可在泄露时限制损害。刷新逻辑应能干净地轮换,并在失败时安全关闭。
- 按需撤销: 会话终止和事件响应应包含使令牌失效并强制重新认证的能力。
- 验证输入并保护传输: HTTPS、在适当情况下使用证书固定以及输入验证都很重要,因为授权可能通过相邻的弱点被绕过。
对于通过应用商店发布应用的团队,认证设计还与 API 的暴露和合规审查相交。这份关于 API 应用商店合规安全标准 的摘要非常适合与您的授权检查清单配合使用。
最后一点容易被忽视。最小权限原则也适用于内部工具。管理面板、支持控制台和预发布应用通常拥有公司中最宽松的控制措施,尽管它们往往暴露了最敏感的操作。
Capacitor 与 Electron 的实现模式
当你不再把 App 当成只是套了层壳的浏览器时,跨平台 App 授权就会容易得多。Capacitor 和 Electron 都需要采用兼顾原生存储、进程边界和重定向处理的模式。

行之有效的 Capacitor 模式
对于 Capacitor,请使用支持带 PKCE 的系统浏览器 OAuth,并能正确处理 deep link 或 app link 回调的插件或认证库。例如以下库: capacitor-oauth2 可以减少大量 code 胶水代码,但前提是你仍然要显式管理 token 存储和刷新行为。
一种实用的结构如下:
- 认证协调器: 发起登录、跟踪状态、处理回调。
- Token 服务: 通过原生安全存储保存 token,而非浏览器存储。
- API 客户端: 附加 access token,刷新时重试一次,遇到无法恢复的失败则强制登出。
- 策略感知后端: 将 token claims 映射到服务端授权校验。
你还需要适配混合应用生命周期的会话工具。如果你正在评估这一层的方案, 用于安全会话管理的 Capacitor 插件 是对比不同方案的好地方。
最简 token 刷新逻辑的伪代码大致如下:
async function authorizedFetch(request) {
let token = await tokenStore.getAccessToken()
let response = await api(request, token)
if (response.status !== 401) return response
const refreshed = await auth.refresh()
if (!refreshed) {
await auth.signOut()
throw new Error('Session expired')
}
token = await tokenStore.getAccessToken()
return api(request, token)
}
重点不在语法,而在于让刷新逻辑保持集中,避免每个页面各自实现一套会话行为。
需要额外留意的 Electron 模式
Electron 需要更严格的边界。尽可能把 token 交换和安全存储放在主进程。向渲染进程暴露范围更窄的 IPC 方法,而不是把原始 token 直接交给渲染进程,然后指望它守规矩。
桌面应用比移动应用更让人感觉可控。把这种感觉当作风险,而不是保障。
避免这些捷径:
- 不要将 token 存到渲染进程可访问的 localStorage 中 如果可以避免的话。
- 不要让每个窗口共享宽泛的认证上下文 而不检查窗口用途和会话生命周期。
- 不要过度依赖 preload 脚本 来替代进程隔离和明确的 API 边界。
运营可见性也很重要。根据 Splunk 关于应用安全要求的概述,应用授权应与持续活动监控和日志记录集成,其中引用的基准数据显示,主动记录并监控授权事件的组织 能在 15 分钟内检测到 95% 的未授权访问尝试。实际上,这意味着记录被拒绝的操作、令牌刷新失败、角色变更、同意撤销以及异常的资源访问模式。
如果您的发布流程包含混合应用更新,该生态系统中的一个选项是 Capgo,它为 Capacitor 和 Electron 应用提供签名的 Live Update。它不会替你实现授权,但当授权逻辑、重定向处理或会话 code 需要紧急修复时,它会影响你发布修复的速度。
实现安全应用授权的路径
好的 App 授权不是单一决策,而是一系列必须相互支撑的决策。你要正确认证用户、只请求需要的访问权限、通过 OAuth 2.0 + PKCE 这类安全流程交换 token、把密钥存到正确的位置,并且每次都在服务端强制执行权限校验。
对 Capacitor 和 Electron 团队来说,最关键的是在边界处保持严谨。浏览器时代的捷径一碰到原生存储、deep link、桌面进程边界或应用审核要求就会失效。不出问题的团队通常没做什么花哨的事,他们只是在 scope、会话处理、服务端校验和可审计性上保持一致。
如果你觉得当前的授权配置很乱,这很正常。从一次收紧一个边界开始:修好流程,修好存储,把访问规则移出 UI,并添加日志,在有人请求不该有的东西时提醒你。
这样,安全的应用授权才变得可控。理论上并不更简单,但在 code 中更安全。
Capgo 能帮助团队无需等待应用商店审核,即可向 Capacitor 和 Electron 应用推送修复。当需要快速修正身份验证流程、token 处理或会话 bug 时,这一点很关键。如果你的团队希望更精细地控制跨平台发布、定向发布和回滚支持, Capgo 值得与你的应用授权技术栈一起评估。