OAuth 2.0是一种授权机制,用户通过授权服务器给第三方应用授权,以访问自己的资源(位于资源服务器)。相对于OAuth 1.0,OAuth 2.0简化了对客户端的验证,只保留了获取Authorization Code和Access Token的请求过程。

OpenID Connect (OIDC)是OAuth 2.0的一个扩展,结合OAuth 2.0的授权提供了用户的认证。

常见的认证方式

基于Password:

  • LDAP, 基于目录的认证, 如Active Directory
  • RADIUS, 网络访问认证,如VPNs, Wi-Fi

基于Certificate / Key:

  • mTLS (Mutual TLS), 客户端/服务端双向X.509证书认证
  • SSH public key, 非对称加密的远程访问认证

基于Token / Web:

  • OAuth 2.0
  • OpenID Connect
  • JWT (JSON Web Token)
  • SAML 2.0

基于Challenge-Response:

  • Kerberos, 基于Ticket的双向认证,是Windows domain认证的核心
  • Digest Auth, 使用Hash身份的HTTP challenge-response认证

无密码:

  • FIDO2 / WebAuthn, 使用硬件Key或生物信息的抗钓鱼的认证
  • Passkeys, 基于FIDO2,通过Apple/Google/Microsoft等平台在终端之间同步的认证

多要素:

  • TOTP (RFC 6238), 基于Time的一次性密码,如Authenticator
  • HOTP (RFC 4226), 基于Counter的OTP

其中,常见的SSO认证方式有OIDC, SAML 2.0, Kerberos。

常见的Token格式

  • JWT(JSON Web Token)
  • Reference Token (随机字符串)
  • SAML Token(XML 格式)
  • MAC Token(OAuth 1.0专用,已废弃)

结合Authorization Code + PKCE的OIDC认证与授权

PKCE (Proof Key for Code Exchange),通过 code_verifier 和 code_challenge 确保只有原始客户端能获得Access Token。

客户端准备
// 客户端生成 code_verifier 和 code_challenge
const code_verifier = generateRandomString(43);  // 43-128 字符
const code_challenge = base64UrlEncode(sha256(code_verifier));

// 保存 code_verifier(sessionStorage 或内存)
sessionStorage.setItem("code_verifier", code_verifier);
用户点击登录

客户端向认证/授权服务器发出HTTP请求如下:

GET /authorize HTTP/1.1
Host: auth.server.com

response_type=code&
client_id=s6BhdRkqt3&
redirect_uri=https://client.example.com/callback&
scope=openid profile email&
state=af0ifjsldkj&
code_challenge=dGhpcyBpcyBhIGNoYWxsZW5nZQ&
code_challenge_method=S256
认证/授权服务器对用户进行认证/授权
  1. 认证/授权服务器显示登录页面
  2. 用户输入用户名/密码
  3. 认证/授权服务器验证凭证
  4. 认证/授权服务器显示授权页面(请求用户授权)
  5. 用户点击 "同意"授权

认证/授权服务器生成Authorization Code,并缓存code_challenge与Authorization Code的关联。

认证/授权服务器重定向

发出HTTP请求如下:

HTTP/1.1 302 Found
Location: https://client.example.com/callback?
  code=SplxlOBeZQQYbYS6WxSbIA&
  state=af0ifjsldkj

返回一次性Authorization Code给客户端。

客户端换取令牌

发出HTTP请求如下:

POST /token HTTP/1.1
Host: auth.server.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
redirect_uri=https://client.example.com/callback&
client_id=s6BhdRkqt3&
code_verifier=dGhpcyBpcyBhIGNoYWxsZW5nZQ

使用一次性Authorization Code和code_verifier请求Token。

认证/授权服务器通过Authorization Code找到对应的code_challenge,然后验证code_verifier有效后,返回如下:

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

其中ID Token是给客户端使用验证用户身份的,根据OpenID Connect规范的要求必须采用JWT格式。

Access Token是给资源服务器鉴定用户的访问权限的,用于请求资源服务器,可以是JWT,也可以是Reference Token。JWT是自包含的,鉴权时无需调用认证/授权服务器;Reference Token的鉴权需要调用认证/授权服务器。Keycloak默认使用Reference Token。JWT是无状态、自包含的,签发后无法主动失效,只能等待过期失效。Reference Token可以根据需要随时注销。

ID Token和Access Token的有效期都较短。

Refresh Token有效期较长,缓存在Redis中,用于生成新的Access Token。Refresh Token过期后,需要重新认证。

客户端验证 ID Token
// verify ID Token
const payload = verifyJwt(idToken, {
  issuer: "https://auth.server.com",
  audience: "s6BhdRkqt3",
  nonce: storedNonce,
  jwksUri: "https://auth.server.com/oauth/jwks"
});

// save to session
sessionStorage.setItem("user_id", payload.sub);
sessionStorage.setItem("user_name", payload.name);
sessionStorage.setItem("access_token", accessToken);

客户端会缓存ID Token,在必要的时候(如敏感请求、用户重新登录等)随时检查ID Token是否过期。ID Token过期后,可以使用Refresh Token请求新的ID Token,此时用户无需登录。

客户端使用Access Token访问资源服务器的资源

发出HTTP请求如下:

GET /api/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
资源服务器向认证/授权服务器验证Access Token(仅Reference Token需要, JWT不需要)

JWT是自包含的,无需验证,但只能到期失效。Reference Token需要验证,但便于随时注销。

发出HTTP验证请求如下:

POST /introspect HTTP/1.1
Host: auth.server.com
Authorization: Basic base64(api-client:secret)

token=2YotnFZFEjr1zCsicMWpAA

认证/授权服务器返回"active": true表示有效,示例如下:

{
  "active": true,
  "scope": "profile email",
  "client_id": "s6BhdRkqt3",
  "username": "taiyangdao@example.com",
  "token_type": "Bearer",
  "exp": 1716123456,
  "iat": 1716120456,
  "sub": "123456789"
}
资源服务器看到有效Access Token,返回被请求的资源
{
  "id": 123,
  "name": "taiyangdao",
  "email": "taiyangdao@example.com"
}
刷新Access Token

Access Token过期后,需要通过Refresh Token请求新的Access Token,HTTP请求如下:

POST /token HTTP/1.1
Host: auth.server.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&
client_id=s6BhdRkqt3&
client_secret=secret

认证/授权服务器返回如下:

{
  "access_token": "new_access_token_xyz",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "new_refresh_token_xyz"
}

每次使用Refresh Token,都会创建新的Refresh Token,但是其有效期仍然从最初认证时间计算。如果Refresh Token过期,则用户必须重新登录。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐