在现代身份认证与授权体系中,OAuth 2.0 和 OpenID Connect(OIDC) 经常被一起提及,但它们解决的是不同层面的问题。理解两者的区别和协作关系,是构建安全、标准的身份系统的关键。


是什么?

OAuth 2.0 不是身份验证协议,而是一个授权框架,允许第三方应用在用户授权下访问受保护资源,而无需获取用户密码。

OpenID Connect(OIDC)是一种基于OAuth 2.0协议构建的‌身份认证标准‌,旨在为现代应用程序提供简单、安全且可互操作的用户身份验证方式。它通过在OAuth 2.0的授权框架之上添加一个身份层,解决了OAuth 2.0本身不完善的身份认证问题。

重要区分

  • OAuth 2.0 = 授权
  • OpenID Connect = 身份认证+授权

OAuth2.0授权流程

OAuth2 定义了好几种授权模式,有Authorization Code、Client Credentials、Device Code、Implicit Flow和Password Credentials,隐式流和密码凭据模式被认为是不安全的,不推荐使用。

核心角色

角色 描述
Resource Owner(资源所有者) 通常是用户自己
Client(客户端) 请求访问用户资源的应用(Web、移动、单页应用SPA 等)
Authorization Server(授权服务器) 颁发令牌(如 Casdoor、Keycloak、Google OAuth)
Resource Server(资源服务器) 托管用户数据(如 API 服务),接受并验证访问令牌,以确定请求是否合法

授权码模式(Authorization Code Flow)

适用于:服务器端 Web 应用、移动 App、单页应用SPA(配合 PKCE 使用)

    sequenceDiagram
    participant User as 用户
    participant Client as 客户端
    participant AS as 授权服务器
    participant RS as 资源服务器

    User->>Client: 访问应用
    Client-->>User: 重定向至授权服务器<br>(client_id, redirect_uri, scope, state, code_challenge)
    User->>AS: 登录并同意授权
    AS-->>User: 重定向回客户端<br>(?code=xxx&state=yyy)
    User->>Client: 浏览器携带授权码
    Client->>AS: POST /token<br>(code, client_id, client_secret*, code_verifier)
    AS-->>Client: 返回 access_token (+ refresh_token)
    Client->>RS: GET /api/data<br>Authorization: Bearer <access_token>
    RS-->>Client: 返回受保护资源

流程

  1. 用户访问客户端程序,如单页应用

  2. 将用户请求重定向至包含一些参数的授权URL

    • response_type 授权模式

    • client_id 授权服务器上注册的客户端ID

    • redirect_uri 重定向地址,返回授权码给客户端

    • scope 请求的权限

    • state 随机字符串,防止CSRF攻击,存储在客户端会话中,并在收到回调时进行验证

  3. 授权链接就是授权服务器的地址,一般显示哪个应用正在请求哪些权限,以及被授予哪个账户。用户批准后授权服务器生成一个授权码

  4. 授权服务器重定向到应用的redirect_uri,并且携带授权码

  5. 应用携带授权码、客户端ID、客户端凭证向授权服务器令牌端点发起POST请求,以换取AccessToken(这次请求是服务器之间进行,不会暴露给浏览器)

  6. 授权码有效未过期的话,授权服务器响应AccessToken

    一般会有如下字段:

    1
    2
    3
    4
    5
    6
    7
    8
    
    {
      "access_token": "ACCESS_TOKEN",
      "token_type": "bearer",
      "expires_in": 123456789,
      "refresh_token": "REFRESH_TOKEN",
      "scope": "read",
      "uid": 111111
    }
    

🔐 安全机制

  • 授权码和访问令牌分离传输,访问令牌不会通过浏览器传输
  • state 参数防 CSRF
  • 授权码一次性 + 短有效期(≤10分钟)

📌 适用场景

  • 所有新项目(无论是否能存储 client_secret
  • 移动 App(使用系统浏览器 + PKCE)
  • SPA(必须用 PKCE,避免隐式模式)

PKCE

PKCE 是授权码流程的安全扩展,防止授权码拦截攻击。对于无法安全存储客户端秘密的公共客户端(移动应用、单页应用)来说,这非常重要。流程上和授权码模式一样,额外多了下面的参数及校验

  1. 客户端生成

    • code verifier:随机字符串

    • code challenge:对上面的verifier哈希

  2. 授权请求,带上code challenge和其hash方法

  3. 拿到授权码后,服务器请求token时,需要带上原始的verifier

  4. 服务器验证

    a. 使用指定的方法对verifier进行hash

    b. 与存储的challenge比较


客户端凭证模式(Client Credentials Grant)

适用于服务到服务通信(无用户参与),直接用客户端凭证向令牌端点发起 POST 请求。

    sequenceDiagram
    participant Client as 客户端(服务A)
    participant AS as 授权服务器
    participant RS as 资源服务器(服务B)

    Client->>AS: POST /token<br>grant_type=client_credentials<br>client_id + client_secret
    AS-->>Client: 返回 access_token
    Client->>RS: 请求资源<br>Authorization: Bearer <access_token>
    RS-->>Client: 返回资源(属于客户端自身)

🔐 安全机制

  • 仅验证客户端身份,不涉及用户
  • 必须通过 HTTPS 传输 client_secret
  • 作用域(scope)严格限制

📌 适用场景

  • 微服务间
  • 后台任务
  • 无用户参与的自动化流程

设备码模式(Device Code Grant)

设备代码授予类型允许对无浏览器或输入能力有限的设备进行授权。用户在独立设备(如手机或电脑)上授权,而受限设备(如电视、物联网设备、命令行工具等)则轮询授权结果。

授权服务器响应一般如下:

1
2
3
4
5
6
7
8
{
  "device_code": "Ab2XXXXXXXXXXXXXXXXXXX1aC", // 用于轮询授权状态
  "user_code": "xxxx", // 用户需要输入的短码
  "verification_uri": "https://a.b.com/device", // 用于用户在独立设备上输入用户码的地址
  "verification_uri_complete": "https://a.b.com/device?user_code=xxxx", // 完整网址,预填了用户码(对二维码展示的地址常用)
  "interval": 10, // 轮询间隔
  "expires_in": 1600 // 有效期
}
    sequenceDiagram
    participant Device as 设备(如电视)
    participant AS as 授权服务器
    participant User as 用户(手机/电脑)
    participant RS as 资源服务器

    Device->>AS: POST /device/code<br>client_id
    AS-->>Device: 返回 device_code, user_code, verification_uri
    Device->>User: 显示“请访问 verification_uri 并输入 user_code”
    User->>AS: 在另一设备登录并输入 user_code
    AS-->>User: 用户确认授权
    Device->>AS: 轮询 /token?grant_type=device_code&device_code=...
    AS-->>Device: 返回 access_token
    Device->>RS: 使用令牌访问资源
    RS-->>Device: 返回数据

流程:

  1. 设备向授权服务器请求设备码和用户码

  2. 设备显示用户码和验证 URL

  3. 用户在另一设备上访问验证 URL 并输入用户码

  4. 用户点击授权

  5. 设备轮询授权服务器获取访问令牌

🔐 安全机制

  • user_code 用户可读短码(如 ABCD-EFGH
  • device_code 随机字符串
  • 短有效期(通常 10-30 分钟)
  • 轮询间隔由服务器控制(防暴力破解)

📌 适用场景

  • 智能电视、游戏主机
  • 命令行工具(如 gh auth login
  • 无浏览器的嵌入式设备

❌ 已弃用的授权模式

⚠️ IETF OAuth 2.0 Security Best Current Practice (BCP) 已明确弃用下面两种模式

模式 问题 替代方案
隐式模式(Implicit) 令牌直接暴露在 URL 中,易被 XSS、日志、referrer 泄露 授权码 + PKCE
密码模式(Password Credentials) 应用需收集用户密码,违反 OAuth 核心原则 授权码 + PKCE

常见安全威胁与防护

1. 授权流程类威胁

威胁 防护措施
授权码截获 强制 PKCE;授权码一次性 + 短有效期
开放重定向 redirect_uri 精确白名单匹配(禁用通配符)
CSRF 攻击 必须使用随机 state 参数并严格校验

2. 令牌管理类威胁

威胁 防护措施
XSS 窃取令牌 Web 应用使用 HttpOnly + Secure + SameSite=Strict Cookie(BFF 架构)
Refresh Token 滥用 实施轮转机制(每次使用后旧 token 失效);设置 ≤30 天有效期
令牌长期有效 Access Token 短期有效(30-60 分钟);支持吊销

3. 客户端实现类威胁

威胁 防护措施
PKCE 缺失 所有公共客户端(SPA/移动)必须启用 PKCE
Client Secret 泄露 公共客户端绝不使用 client_secret;机密客户端存于密钥管理服务
Clickjacking 授权页面设置 X-Frame-Options: DENY

OIDC

在OAuth2.0的基础上,标准化身份信息格式,必须有用户参与登陆。

OIDC复用OAuth2.0的授权码流程,仅增加了两个参数:

  1. 请求:scope=openid profile email,必须包含openid(这是OIDC的标志),其他是可选获取更多用户信息

  2. 响应:额外返回id_token

id_token是JWT格式的,包含标准声明(claims),由授权服务器签名(通常RS256),客户端可以本地验证,无需调用服务器。

ID Token结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "iss": "https://auth.example.com",     // 签发者(Issuer)
  "sub": "abc",                          // 用户唯一标识(Subject)
  "aud": "my-client-id",                 // 受众(Audience,即 client_id)
  "exp": 1712345678,                     // 过期时间
  "iat": 1712342078,                     // 签发时间
  "auth_time": 1712342000,               // 用户认证时间
  "email": "user@example.com",           // 可选:用户邮箱
  "name": "John Doe"                     // 可选:用户名
}

OIDC的几个概念说明

概念 说明
OpenID Provider (OP) 实现 OIDC 的授权服务器(如 Casdoor、Keycloak、Google)
Relying Party (RP) 使用 OIDC 进行登录的客户端(即你的应用)
ID Token JWT 格式的身份令牌,证明用户已认证
UserInfo Endpoint 可选:通过 access_token 获取更详细的用户信息(GET /userinfo
Discovery Document 标准化元数据端点(.well-known/openid-configuration),自动发现 OP 配置

授权流程

大致和OAuth2.0授权码模式一样

  1. 发起授权请求

  2. 处理回调,交换令牌

  3. 验证ID Token(关键步骤)

  4. 拿access_token获取用户详细信息(可选)

    sequenceDiagram
    participant User as 用户
    participant Client as 客户端
    participant AS as 授权服务器(OIDC Provider)
    participant RS as 资源服务器

    User->>Client: 访问应用
    Client-->>User: 重定向至 AS<br>scope=openid+profile+email<br>response_type=code<br>client_id=xxx
    User->>AS: 登录并同意
    AS-->>User: 重定向回客户端<br>?code=AUTH_CODE
    User->>Client: 浏览器携带 code
    Client->>AS: POST /token<br>grant_type=authorization_code<br>code=AUTH_CODE<br>client_id/secret
    AS-->>Client: 返回<br>access_token + id_token(JWT)
    Client->>Client: 验证 id_token 签名 & claims
    Client->>RS: 用 access_token 请求用户数据
    RS-->>Client: 返回资源

最佳实践

  1. 统一使用授权码 + PKCE:无论客户端类型,这是当前最安全的通用方案。
  2. 一般不手写 OAuth 流程:使用成熟库。
  3. 前端 0 令牌原则:通过 BFF(Backend for Frontend)架构,让浏览器只持 Cookie,令牌由后端管理。
  4. 最小权限原则:仅请求必要 scope,定期审查权限。
  5. 全链路 HTTPS:从授权到资源访问,全程 TLS 加密。
  6. 验证重定向URI:精确匹配注册的URI,不使用通配符。
  7. 监控与吊销:记录授权行为,支持用户一键撤销所有会话。

参考 DigitalOcean OAuth 2.0 指南