OAuth 2.0 和 OpenID Connect 有什么区别?授权码流程如何工作?
简化版
OAuth 2.0 解决「授权委托」——让用户授权第三方客户端代表自己访问资源(拿 access token 访问资源服务器),它本身不规定如何标准化地获取「用户是谁」。OpenID Connect(OIDC)建在 OAuth 2.0 之上,补上「身份认证层」——增加 openid scope 和 ID Token,让客户端能标准化地确认「当前登录用户是谁」。授权码流程:用户在授权服务器登录授权 → 客户端拿到 code(授权码)→ 后端用 code 换 token(走后端通道,token 不暴露在浏览器);公开客户端(SPA/移动端)还要用 PKCE 防授权码被截获。
详细版
OAuth 2.0 vs OIDC:
| OAuth 2.0 | OpenID Connect | |
|---|---|---|
| 解决 | 授权(能不能访问资源) | 认证(用户是谁) |
| 核心产物 | access token(给资源服务器) | + ID Token(给客户端识别用户) |
| 类比 | 「允许代我访问」 | 「证明我是谁」 |
OAuth 2.0 四个角色:
- 资源所有者(用户)、客户端(第三方应用)、授权服务器(发 token)、资源服务器(受保护的 API)。
授权码流程(Authorization Code Flow):
① 客户端重定向用户到授权服务器
/authorize?client_id=..&redirect_uri=..&scope=openid..&state=..&code_challenge=..(PKCE)
② 用户在授权服务器登录 + 授权
③ 授权服务器重定向回 redirect_uri,带上 code(授权码)
④ 客户端【后端】用 code + client_secret(或 PKCE code_verifier) 到 token endpoint 换 token
⑤ 拿到 access token(+ OIDC 的 ID Token)
两种 token 别混用:
- access token → 受众是资源服务器,用来访问 API。
- ID Token → 受众是客户端,用来证明用户身份。
完整版教学
一、OAuth2 不是登录协议本身
OAuth 2.0 的原始目标是「授权委托(delegation)」:用户允许某个客户端代表自己访问资源。经典场景:「用微信登录」授权某 App 读取你的微信头像昵称——你授权 App 代你访问微信的资源。
关键点:OAuth2 关心的是「客户端能不能访问资源服务器」,它不直接规定「客户端如何获得标准化的用户身份信息」。很多人误把 OAuth2 当「登录协议」用(拿 access token 去解析用户信息),但 access token 的格式和内容 OAuth2 没标准化——这正是 OIDC 要补的。
二、OIDC 补上身份层
OpenID Connect(OIDC)在 OAuth2 之上定义了「身份认证层」。当客户端在授权请求里带上 openid scope 时,授权服务器除了 access token,还会返回一个 ID Token:
- ID Token 是一个 JWT,包含标准的身份声明:
iss(发行方)、sub(用户唯一标识)、aud(受众=客户端)、exp(过期)、nonce等。 - 客户端验证 ID Token 后,就能标准化地知道「登录用户是谁」。
所以:OAuth2 管授权、OIDC 管身份。「用 XX 登录」这类第三方登录,本质是 OIDC(在 OAuth2 授权的基础上,用 ID Token 拿到用户身份)。
| 产物 | 发给谁用 | 主要用途 | 常见错误 |
|---|---|---|---|
| Authorization Code | 客户端后端或公开客户端 + PKCE | 换 token 的一次性凭证 | 当成访问凭证 |
| Access Token | 资源服务器 | 访问 API | 客户端解析它当用户资料 |
| ID Token | 客户端 | 识别登录用户 | 资源服务器拿它当 API 凭证 |
OAuth2/OIDC 最稳的记法:access token 给 API,ID Token 给客户端,code 只负责换 token。
三、授权码流程步骤
授权码流程是最标准、最安全的流程:
- 客户端把用户重定向到授权服务器,携带
client_id、redirect_uri、scope(含openid)、state、code_challenge(PKCE)等参数。 - 用户在授权服务器登录并授权(同意客户端访问哪些资源)。
- 授权服务器重定向回客户端的
redirect_uri,带上code(授权码)——这个 code 是一次性的、短期有效的。 - 客户端后端用 code 去 token endpoint 换 token——带上
code+client_secret(机密客户端)或code_verifier(PKCE,公开客户端)。 - 拿到 access token(+ OIDC 的 ID Token)。
四、为什么不直接在前端发 token(授权码的价值 + PKCE)
为什么要绕这么一圈、用 code 换 token,而不直接返回 token?
- 授权码流程让 token 从「后端通道」返回,而不是直接出现在浏览器的 URL / 重定向里。这减少了 token 暴露在浏览器地址栏、历史记录、Referer、日志中的风险。(早期的「隐式流程 Implicit Flow」直接在 URL 返回 token,不安全,已不推荐。)
PKCE(Proof Key for Code Exchange) 保护公开客户端(SPA、移动端——它们无法安全保存 client_secret,因为代码在用户设备上):
- 客户端先生成一个随机的
code_verifier,算出它的哈希code_challenge,在第①步授权请求里带上code_challenge。 - 第④步换 token 时带上原始的
code_verifier,授权服务器验证code_verifier的哈希是否等于当初的code_challenge。 - 即使授权码 code 被截获(如通过恶意 App 拦截 redirect),攻击者没有
code_verifier,也换不到 token。
code_verifier = 随机 43~128 字符
code_challenge = BASE64URL(SHA256(code_verifier))
授权请求带 code_challenge,换 token 时带 code_verifier
PKCE 现在是所有客户端(包括机密客户端)的推荐做法。
五、state 和 nonce 的作用(生产不能省)
state:客户端发起授权请求时生成一个随机值,回调时校验是否一致。防授权响应被 CSRF / 篡改(防止攻击者把自己的 code 塞给受害者)。nonce(OIDC 用):绑定「认证请求」和「返回的 ID Token」——客户端生成 nonce 放进请求,ID Token 里会带回同样的 nonce,客户端校验。防重放攻击(防止旧的 ID Token 被重放)。
state 和 nonce 不是可有可无的装饰参数——生产环境必须用,否则有 CSRF 和重放风险。
六、Access Token 和 ID Token 别混用(高频考点)
这是最容易搞错的点:
- Access Token:受众是资源服务器,用来访问 API。资源服务器验证它、决定放不放行。它可能是不透明字符串或 JWT,客户端不应该解析它来获取用户身份(它的内容是给资源服务器的)。
- ID Token:受众是客户端,用来证明用户身份。客户端解析它知道用户是谁。资源服务器不应该拿 ID Token 当访问凭证。
混用是错误的:资源服务器不该收 ID Token 当 API 凭证;客户端不该从 access token 里随意解析用户信息。access token 给 API 用、ID Token 给客户端识别用户用——各司其职。
七、Spring Security 中的落地
Spring Security 里,应用可能扮演不同角色,配置不同:
- 作为客户端(做第三方登录):用
oauth2Login()——处理登录、授权码回调、拿 ID Token 识别用户。 - 作为资源服务器(保护 API):用
oauth2ResourceServer().jwt()——校验请求头里的 Bearer access token。
两者职责不同:一个处理「登录和授权码回调」,一个处理「保护 API」。配置时要明确当前应用扮演哪个角色,别混在一起。
八、常见误区与追问
- 误区:OAuth2 本身就是标准登录协议。 OAuth2 主要解决授权委托,OIDC 才在其上标准化用户身份认证。
- 误区:Access Token 和 ID Token 可以混用。 Access Token 给资源服务器访问 API,ID Token 给客户端识别用户,受众和用途不同。
- 误区:授权码拿到后就可以直接访问 API。 授权码是短期一次性凭证,只能到 token endpoint 换 token。
- 追问:PKCE 解决什么问题? 它防止授权码被截获后被攻击者换成 token,攻击者没有
code_verifier就无法完成交换。 - 追问:
state和nonce分别防什么?state绑定授权请求和回调防 CSRF,nonce绑定认证请求和 ID Token 防重放。 - 追问:Spring 应用做第三方登录和保护 API 分别怎么配? 第三方登录用
oauth2Login(),资源服务器保护 API 用oauth2ResourceServer().jwt()。
九、加强记忆
OAuth 2.0 管「授权」(让客户端代表用户拿 access token 访问资源,四角色:资源所有者/客户端/授权服务器/资源服务器),不标准化用户身份;OIDC 建在其上补「认证层」(openid scope + ID Token,让客户端知道「用户是谁」,第三方登录本质是 OIDC)。授权码流程:重定向到授权服务器登录授权 → 拿 code → 后端用 code 换 token(走后端通道,token 不暴露浏览器);公开客户端(SPA/移动端)用 PKCE(code_verifier/code_challenge,即使 code 被截获也换不到 token);state 防 CSRF、nonce 防重放(生产必用)。access token 给资源服务器访问 API、ID Token 给客户端识别用户,别混用。Spring:客户端用 oauth2Login、资源服务器用 oauth2ResourceServer().jwt()。