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


是什么?

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

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

核心角色

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

重要区分

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

OAuth2.0授权流程

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

授权码模式(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. 服务器验证

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

    2. 与存储的challenge比较

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

适用于:服务到服务通信(无用户参与)。

    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)

适用于:无完整输入/显示能力的设备(如智能电视、IoT 设备)。

    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 指南