我开新项目时,最先要做的事情之一就是把登录系统接上。我的目标很明确:不想从零开始造并维护一套用户数据库。最自然的方案就是让用户直接用现有账号登录,比如 Google。听起来很简单,但实际做下来,我被带进了 OAuth2 和 OpenID Connect 的学习路径。这篇文章就是我一路摸索后的总结。
Provider 和 Client 到底是谁
我最先卡住的是术语。找库的时候总看到 “OAuth2 Provider” 和 “OAuth2 Client”,但一开始我并不确定自己到底要哪一个。
- OAuth2 Provider 是管理用户账号并签发 access token 的服务。比如 Google 就是 provider。给“provider 侧”用的库,是给你在自己系统里做身份源、让别的应用来接入时用的。
- OAuth2 Client 是需要从 provider 获取用户数据的应用。我的应用是要用 Google 登录,所以我的应用是 client。
这是第一个关键分界线。我不需要自己实现 provider;我需要的是把 client 流程跑通,接到现成的 provider 上。
拆开看 Authorization Code Flow
为了理解 client 库到底帮我做了什么,我决定先看一遍底层 HTTP 请求。对 Web 服务端来说,最常见的是 Authorization Code Flow。 我当时理解的步骤是这样:
- 发起登录:用户点击“登录”后,我的服务端会把用户重定向到 provider 的授权 URL。这个 URL 会带上我应用的 client_id、redirect_uri(provider 回跳地址)、请求权限范围 scope,以及一个用于安全校验的随机 state 值。
- 用户授权:用户跳到 provider 页面(比如 Google 登录页),完成登录并同意我请求的权限。
- 拿到 code:provider 把用户重定向回我的 redirect_uri,并在 URL 里附带一个临时 code。我的服务端必须校验回传的 state 是否和最初发出去的一致。
- 用 code 换 token:然后我的服务端会向 provider 的 token endpoint 发一个服务端到服务端的 POST 请求,带上 code、client_id 和 client_secret。校验通过后,provider 会返回 access token。
这个 POST 请求用 curl 看起来大概是这样:
curl -X POST https://provider.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=TEMPORARY_CODE" \
-d "redirect_uri=https://myapp.com/callback" \
-d "client_id=MY_CLIENT_ID" \
-d "client_secret=MY_CLIENT_SECRET"
拿到 access token 之后,我的应用就可以拿它去调 provider 的 API,读取用户信息,比如姓名和邮箱。
其他 OAuth2 Flow
Authorization Code flow 并不是唯一选择。我后来也了解到,不同类型的应用会用不同 flow:
- Authorization Code with PKCE:这是给无法安全保存 client_secret 的客户端准备的扩展,比如移动端应用或者浏览器里的 SPA。
- Implicit Flow:这是以前 SPA 常用、但安全性更弱的老方案,现在基本都被 PKCE 替代。
- Device Code Flow:适用于没有浏览器或文本输入不方便的设备,比如智能电视。
用 Playground 动手更快理解
光看文档和流程图是一回事,自己走一遍请求又是另一回事。我发现了一个很好用的工具:oauth.com/playground。它可以一步步演示不同 OAuth2 flow。我用它把请求和响应都看了一遍,对整个流程里每个环节是怎么配合的,理解扎实了很多。
用 OpenID Connect 把授权和认证接起来
接下来一个很关键的认知是:OAuth2 和 OpenID Connect(OIDC)并不是一回事。以前我几乎把这两个词混着用,但它们关注点不同。
- OAuth2 是授权(authorization)框架,核心是“允许你访问哪些资源”。access_token 告诉 API:这个 client 被允许做什么。
- OpenID Connect 是构建在 OAuth2 之上的认证(authentication)层,核心是“这个用户是谁”。
OIDC 的流程看起来和 OAuth2 的 Authorization Code flow 几乎一样。关键区别在 token endpoint 的响应:除了 access_token,OIDC 还会返回 id_token。 这个 id_token 是一个 JSON Web Token(JWT),里面包含用户身份相关的 claims,比如用户唯一 ID、邮箱、姓名。它让“登录”这件事真正成立,因为它是 provider 已完成身份认证的可验证凭据。
验证 ID Token
拿到 id_token 并不代表流程结束。我的应用不能盲信里面的内容,必须先验证 token 的签名,确认它真实且没被篡改。 这个过程大致是:
- 身份提供方用私钥给 id_token 签名。
- 提供方会在一个公开 URL 提供对应公钥,这个地址叫 JSON Web Key Set(JWKS)endpoint。
- 我的应用从 JWKS endpoint 拉取公钥。
用 curl 拉取 key set 很简单:
curl https://provider.com/.well-known/jwks.json
- 然后用这个公钥去验证 id_token 的签名。
只有签名校验通过后,我的应用才会信任 token 里的身份 claims,并正式把用户登录进来。这一步是整个登录链路安全性的最后一道门。