继续原理篇的话题。本篇从 OAuth 诞生的痛点开始,完整介绍如何在一个 SaaS 系统中接入 Google 和 Apple 社交登录,覆盖 Web 应用和 Chrome Extension 两种场景。
多平台的痛点 → OAuth 的诞生
进入 Web 2.0 时代,资源提供方越来越多。用户在不同平台需重复注册账号和设置密钥,带来两大问题:
- 效率低:频繁在多个平台注册账户,操作繁琐。
- 安全差:用户在不同平台设置同样的访问密钥,一个平台泄露,所有平台都暴露。
OAuth 方式认证应运而生,它分两个部分:
- 认证(Authentication):为用户生成全局唯一标识,验证用户身份;
- 授权(Authorization):代理用户授权给第三方平台使用用户信息(如用户标识、昵称等),用户无需直接注册、也无需重复提交信息或设置访问密钥。
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│用户 account │────▶│ 资源提供方 │────▶│ 认证平台 │
│ │ │ Resource Owner │ │ Auth │
└─────────────┘ └──────────────────┘ └─────────────┘
用现实场景理解信任链
我和系统张三互相认识。我想从系统李四那里借钱,但我和李四不熟。好在系统张三人人认识,我和李四都信任张三。李四从张三那里确认我的身份后,我就可以借钱了。
后续多了王五、赵六也一样。我在不同平台使用同一个身份,并且可以控制——张三不能把我存钱在别处的秘密告诉李四。这就是授权和信任传递。
支持多社交账号登录的账号系统
核心思路
在全球范围内,大部分互联网用户要么使用 Google(Gmail),要么使用 Apple(Apple Mail),或者两者都用。因此,如果 SaaS 系统接入它们的账号体系,就意味着支持了全球大多数用户。这类第三方账号统称为 Social Account。
结合"一处注册,到处可用"的思路,引入一个第三方角色:
Google / Apple
Social Account System
│
┌───────────────┼───────────────┐
│ │ │
Platform A Platform B Platform C
用户只需在 Google/Apple 注册一次账号,就可以在多个平台使用。
信任链模型
用户 aa 在 Google 注册了账号(email + password),Google 分配唯一 ID。
用户 aa 想在应用系统 bar 上使用服务,只需让 bar 去 Google 确认 "我是 aa"。
这个过程包含三个关键点:
- 认证:验证用户身份
- 授权:让用户控制哪些信息可以共享
- 信任传递:认证平台作为可信第三方
OAuth 授权码模式 vs 隐式模式
在进入实战之前,先了解两种主流的 OAuth 流程。它们在不同的客户端场景下各有用途:
- Authorization Code Flow(授权码模式):前端拿到一次性 code,后端再用 code + client_secret 换 token。更安全,适合有后端的 Web 应用。
- Implicit Flow(隐式模式):直接返回 id_token,不需要 client_secret,适合纯前端应用或没有后端的场景(如 Chrome Extension)。
下面你会在 Web 应用中看到授权码模式的应用,在 Chrome Extension 中看到隐式模式的应用。
实战:Sign in with Google
Google Cloud 项目配置
前往 Google Cloud Console 新建项目。
关键概念:
- 一个 Project 可以对应多个 OAuth Client ID,以适配多种客户端类型(Web, iOS, Android, Extension)
- 同一个 Project 内,不同 Client ID 生成的用户唯一标识是一样的。也就是说,用户 foo 在 Web 客户端上使用 Gmail 注册得到 UID,在 iOS / Extension 端登录得到的 UID 值相同。
- 一个 OAuth Client ID 可以生成多个对应的 Client Secret。
- Client ID 可以对外公开(实际编码中一定会公开),但 Client Secret 绝对不能对外公开。
Web 应用配置
在 Google Cloud 左侧导航栏进入 APIs & Services → Credentials,点击顶部 Create Credentials → OAuth Client ID:
- Application type: Web application
- Name: 随便写
配置 Authorized JavaScript origins:
- 本地测试 URI:
http://localhost:8787 - 生产环境域名 URI:
https://foo.bar
保存后记录下 Client ID 和 Client Secret。
Web 应用编码
完整流程:
[Main Web App] ───▶ Opens Popup ──▶ [Google Auth Server]
▲ │
│ │ Authenticates User
└─── postMessage(auth_code) ◄─────────────┘
分步说明:
- 页面渲染 — 渲染登录按钮,传入
client_id - 浏览器侧 — Google Identity Services 弹窗,让用户选账号,拿到一次性授权码
code - 后端实现
/api/account/oauth/google- 接收浏览器 POST 过来的授权码
code - 用
code + client_id + client_secret向 Google 换id_token - 校验
id_token(验签、校验aud) - 写入或更新
account和social_account表 - 写入浏览器 session cookie
- 接收浏览器 POST 过来的授权码
- 浏览器跳转 — 登录成功,重定向到应用首页
Chrome Extension 应用配置
OAuth Client 的配置可以复用 Web 的,也可以独立新建。复用便于管理,独立便于统计不同客户端的注册登录数据。下面以独立新建为例。
在 Google Cloud 新建 OAuth Client:
- Application type: 选 Web application(而非 Chrome Extension)
注意:Application type 选 Chrome Extension 在非 Chrome 浏览器下不通用。如果你的目标是跨浏览器(Edge、Chrome、Safari),应选择 Web application。参考 Chrome Extension OAuth 文档
保存后记录 Client ID 和 Client Secret。
生成 Extension 骨架代码后,用 Chrome 打开 chrome://extensions/,勾选 Developer mode,点击 Load unpacked 加载已解压插件,得到 Extension ID。
重要:Extension ID 和已解压路径有关,路径变了 ID 也会变。如果需要固定 ID,参考 Keep consistent ID。
修改 OAuth Client 配置,在 Authorized redirect URIs 中添加:
https://<extension-id>.chromiumapp.org/
基于 Vite 构建时,配置环境变量:
VITE_SIGN_IN_WITH_GOOGLE_CLIENT_ID=xxx-ooo.apps.googleusercontent.com
VITE_SIGN_IN_WITH_GOOGLE_REDIRECT_URI=https://<extension-id>.chromiumapp.org/
Chrome Extension 应用编码
参考文档:
- Chrome Extension OAuth
- Google Identity Platform - Chrome Extension
- Google OpenID Connect
- MDN - WebExtensions identity API
完整流程:
Extension Google Backend (/xxx.AccountService/Auth)
│ launchWebAuthFlow │ │
│──────────────────────▶ │ consent screen │
│ #id_token=<JWT> │ │
│◄────────────────────── │ │
│ auth({provider:"google;<EXT_ID>", idToken}) │
│──────────────────────────────────────────────▶│
│ │ GET /oauth2/v3/certs │
│ │◄─────────────────────│ (fetch JWKS)
│ │─────────────────────▶│ verify sig + iss + aud + exp
│ { jwt } │
│◄──────────────────────────────────────────────│
│ store jwt, reload tab │ │
分步说明:
-
插件渲染登录页面按钮,生成授权链接:
https://accounts.google.com/o/oauth2/auth ?client_id=<VITE_SIGN_IN_WITH_GOOGLE_CLIENT_ID> &response_type=id_token ← implicit flow(无 code,无 secret) &redirect_uri=https://<ext-id>.chromiumapp.org/ &scope=openid email profile -
插件调用
browser.identity.launchWebAuthFlow()弹窗让用户授权 -
用户授权后,插件重定向到
https://<ext-id>.chromiumapp.org/#id_token=<JWT>&... -
插件从 URI 提取
id_token,向后端发起auth()请求 -
后端实现
auth():- 验证
id_token(JWKS 验签、校验iss/aud/exp) - 写入或更新
account和social_account表 - 生成自有服务的、经过签名的 JWT,返回给插件后续使用
- 验证
-
插件存储 JWT,刷新页面
实战:Sign in with Apple
基本概念
官网文档:
Apple 的账号体系层级关系:
一个 Apple Developer Account
└── 可以生成多个 App ID
└── 多个 App ID 可以归为一组
└── 其中一个为 Primary App ID
└── 同一个 Primary App ID 下,账户 UID 相同
└── 一个 App ID 可以关联多个 Service ID
└── 每个 Service ID 可对应不同的平台(Web, iOS 等)
应用配置
前提:付费订阅 Apple Developer Program。
进入 Certificates, Identifiers & Profiles 进行配置:
Step 1 — 新建 App ID
例如 info.my-domain.app
Step 2 — 新建 Service ID
例如 info.my-domain.web
- 勾选启用 Sign in with Apple
- 修改 Web Authentication Configuration:
- 关联 Primary App ID
- Register Website URLs:
- Domains and Subdomains:添加 Web 应用域名,如
foo-bar.ngrok-free.app - Return URLs:添加 Web 处理回调链接,如
https://foo-bar.ngrok-free.app/api/account/oauth/apple注意:链接必须是 HTTPS 开头。本地开发可使用 ngrok 等工具临时映射到公网。个人开发账号最多关联 10 个链接,企业开发账号可关联 100 个。
- Domains and Subdomains:添加 Web 应用域名,如
Step 3 — 新建 Key
前往 Auth Keys,用于校验用户授权登录后 Apple 生成的 JWT。
- Key Name:如
my-domain - Key Usage Description (optional):
my-domain sign in with xxx - 勾选启用 Sign in with Apple → Configure Key → 勾选前面新建的 Primary App ID
Step 4 — 完成设置
Depending on your product, you may need to configure multiple components for Sign in with Apple – from registering domains for Web Authentication to providing email sources to communicate with your users through the Private Email Relay service. Learn more
应用编码
参考文档:Sign in with Apple REST API
后端流程:
/api/account/oauth/apple— 接收登录回调- 用户授权登录后得到
code,后端用code向 Apple 服务器校验,换取id_token - 使用 Apple 公钥校验
id_token签名,解释得到账号信息 - 写入或更新
account和social_account表
常见问题:
| 现象 | 原因 |
|---|---|
invalid_request / “Invalid client id or web redirect url” |
Client ID 或 Redirect URI 配置不匹配 |
| 授权后弹窗没有自动关闭 | APPLE_OAUTH_REDIRECT_URI 配置错误 |
如果遇到"发生错误,无法完成你的请求"提示,首先检查 Redirect URI 是否完全一致(包括 HTTPS 前缀、路径、无多余斜杠)。
Apple:跨团队迁移应用和用户
如果你需要将应用和用户迁移到另一个 Apple Developer 账号:
总结
本篇从多平台密码管理的痛点出发,完整介绍了如何通过 OAuth + 社交登录解决这些问题。核心要点:
- OAuth 的核心价值:一处认证,多处使用;平台隔离,一处泄露不会波及全部。
- 信任传递:通过 Google/Apple 等可信第三方,实现跨平台身份验证。
- 实战要点:
- Google Web 应用走 Authorization Code Flow(code + secret)
- Chrome Extension 走 Implicit Flow(直接返回 id_token)
- Apple 登录需注意 HTTPS + Redirect URI 精确匹配
- 安全底线:Client Secret 不暴露、JWT 验签、生产环境限制域名。