Featured image of post OAuth 2.0 和 OpenID Connect:现代身份验证指南Featured image of post OAuth 2.0 和 OpenID Connect:现代身份验证指南

OAuth 2.0 和 OpenID Connect:现代身份验证指南

OAuth 2.0 和 OpenID Connect 的完整指南,涵盖授权流程、PKCE、ID 令牌与访问令牌、刷新令牌轮换以及主要提供商的实施。

OAuth 2.0 和 OpenID Connect 构成了现代 Web 身份验证和授权的支柱。尽管它们无处不在,但这些协议经常被误解和错误配置,导致出现可预防的安全漏洞。本指南涵盖了将身份验证安全地集成到应用程序中所需的核心概念、实施模式和安全最佳实践。

OAuth 2.0 基础知识

OAuth 2.0 是一个授权框架,而不是一个身份验证协议。这种区别至关重要:OAuth 定义了客户端应用程序如何获得对受保护资源的委派访问权限,但它没有指定如何验证用户的身份。这就是 OpenID Connect 的用武之地。

该协议定义了四种角色:

角色描述示例
资源所有者可以授予访问权限的实体最终用户
客户请求访问的应用程序网络应用程序、移动应用程序
授权服务器身份验证成功后颁发令牌Auth0、Keycloak
资源服务器托管受保护的资源API服务器

所有授权类型的抽象流程都是一致的:客户端请求授权,资源所有者授予同意,授权服务器颁发令牌,客户端使用这些令牌访问资源。


使用 PKCE 的授权代码流程

对于大多数应用程序(包括本机移动应用程序和单页应用程序),建议授予使用 PKCE 的授权代码流。 PKCE(代码交换证明密钥)通过加密质询将授权请求绑定到令牌请求,从而防止授权代码拦截攻击。

// Step 1: Generate code verifier and challenge
function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString("base64url");
  const challenge = crypto
    .createHash("sha256")
    .update(verifier)
    .digest("base64url");
  return { verifier, challenge };
}

// Step 2: Redirect user to authorization server
const { verifier, challenge } = generatePKCE();
const authUrl = new URL("https://authorization-server.com/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", CALLBACK_URL);
authUrl.searchParams.set("code_challenge", challenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", crypto.randomUUID());

// Step 3: Exchange authorization code for tokens
async function exchangeCode(code, verifier) {
  const response = await fetch("https://authorization-server.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: CALLBACK_URL,
      client_id: CLIENT_ID,
      code_verifier: verifier,
    }),
  });
  return response.json(); // { access_token, refresh_token, id_token, expires_in }
}

state 参数提供 CSRF 保护。现在甚至建议服务器端 Web 应用程序使用 PKCE,因为它提供了针对授权代码拦截的深度防御。


补助金类型比较

补助金类型使用案例安全简介
授权码+PKCE网络、移动、SPA最强 - 推荐用于所有新应用
客户凭证服务器到服务器、服务帐户无用户上下文,安全后端通道
设备授权CLI、物联网、智能电视要求用户访问另一台设备上的 URL
授权码(无 PKCE)旧版服务器端应用程序易受代码拦截
隐式(已弃用)传统 SPA从规范中删除 — 不再安全

OpenID 连接层

OpenID Connect (OIDC) 在 OAuth 2.0 之上添加了身份层。关键的添加是 id_token,这是一个 JWT,其中包含有关经过身份验证的用户的经过验证的声明。

// Verifying an ID token in Node.js
const jwksClient = require("jwks-rsa");
const jwt = require("jsonwebtoken");

const client = jwksClient({
  jwksUri: "https://authorization-server.com/.well-known/jwks.json",
});

async function verifyIdToken(idToken) {
  const decoded = jwt.decode(idToken, { complete: true });
  const key = await client.getSigningKey(decoded.header.kid);
  const signingKey = key.getPublicKey();

  return jwt.verify(idToken, signingKey, {
    issuer: "https://authorization-server.com",
    audience: CLIENT_ID,
    algorithms: ["RS256"],
  });
}

标准 ID 令牌声明包括 sub(主体标识符)、iss(发行者)、aud(受众)、exp(过期)、iat(发行于)和 nonce(防止重放)。在信任 ID 令牌之前,请务必验证签名、颁发者、受众和过期时间。


ID 令牌与访问令牌

一个常见的混淆来源是 ID 令牌和访问令牌之间的区别。 ID 令牌用于身份验证——它们告诉客户端用户是谁。访问令牌用于授权——它们告诉资源服务器允许客户端做什么。

ID 令牌绝不能用于 API 授权。访问令牌可以是不透明的(资源服务器通过自省验证的随机字符串)或 JWT(带有嵌入式声明的独立的)。使用 userinfo 端点从授权服务器检索有关用户的其他声明。


刷新令牌轮换

刷新令牌轮换是一种安全机制,每次刷新操作都会返回一个新的刷新令牌并使前一个刷新令牌失效。这限制了刷新令牌被盗的机会之窗。

async function refreshAccessToken(refreshToken) {
  const response = await fetch("https://authorization-server.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
    }),
  });
  const tokens = await response.json();
  // Old refresh token is invalidated; store the new one
  await secureStore.save("refresh_token", tokens.refresh_token);
  return tokens.access_token;
}

包括 Auth0、Okta 和 Keycloak 在内的主要提供商现在默认启用刷新令牌轮换。与重用检测(如果重用轮换令牌则撤销所有令牌)相结合,这提供了针对令牌盗窃的强大保护。


安全最佳实践

令牌存储策略是最重要的安全决策之一。对于基于浏览器的应用程序,将访问令牌存储在内存中并使用 httpOnly cookie 来刷新令牌。切勿将令牌存储在 localStorage 中,因为同一来源的任何 JavaScript 都可以访问它。

储存方法访问令牌刷新令牌
内存中变量最适合 SPA(不持久)不适合
httpOnly cookie不推荐最佳 — 免受 XSS
本地存储容易遭受 XSS容易遭受 XSS
安全内存(WebAuthn)新兴方法新兴方法

始终在服务器端验证重定向 URI,使用短期访问令牌(15-60 分钟),并将请求范围最小化到功能所需的最低限度。


结论

OAuth 2.0 和 OpenID Connect 是功能强大但微妙的协议。对所有新应用程序使用带有 PKCE 的授权代码流程,在客户端彻底验证 ID 令牌,实施刷新令牌轮换,并且绝不将令牌存储在 localStorage 中。通过遵循这些模式并了解每种授权类型的安全属性,您可以构建用户友好且可抵御常见攻击的身份验证系统。