OAuth2 dPoP Delphi

· 功能

OAuth 2.0 访问令牌是访问 API 的密钥——一旦被盗,任何人都可以在任意位置使用它。DPoP(持有证明,Demonstrating Proof of Possession)定义于 RFC 9449,通过将令牌与请求方进行密码学绑定来解决这一问题。即使令牌被截获,没有客户端私钥也将毫无用处。

sgcWebSockets 2026.4.0 起,OAuth2 客户端和服务器组件均支持完整的 DPoP 功能。本文介绍 DPoP 的概念、工作原理以及在 Delphi 应用中的配置方法。

什么是 DPoP?

在标准 OAuth 2.0 中,Bearer 令牌如同现金——持有者即可使用。如果攻击者通过受损网络、泄露日志或恶意代理截获令牌,则可完全访问受保护资源。

DPoP 将令牌变成需要 PIN 的信用卡。令牌本身绑定到一个公钥,每次请求都必须附带由对应私钥签名的证明 JWT。服务器在接受请求前会验证证明与绑定密钥是否匹配。

Bearer 令牌(传统方式)
持有令牌即可使用。被盗令牌可被完全利用,无需证明身份。
DPoP 令牌(发送方约束)
令牌绑定到密钥对。每次请求都需附带新鲜的签名证明。没有私钥,被盗令牌毫无价值。

DPoP 工作原理

DPoP 流程在标准 OAuth2 流程中增加了一个轻量级密码学步骤:

1. 密钥生成 — 客户端生成非对称密钥对(ES256 或 RS256)。私钥留在客户端;公钥以 JWK 形式包含在 DPoP 证明中。

2. 令牌请求 — 请求访问令牌时,客户端在 HTTP 请求头中附加 DPoP,其中包含已签名的 JWT 证明。证明包含 HTTP 方法、URL、时间戳和唯一标识符。

3. 令牌绑定 — 授权服务器验证证明签名,提取 JWK 指纹(公钥的 SHA-256 哈希),并将其绑定到颁发的令牌上。响应中 token_typeDPoP 而非 Bearer

4. 资源访问 — 每次 API 调用时,客户端同时发送令牌和新鲜的 DPoP 证明。证明包含 ath 声明(访问令牌的 SHA-256 哈希),将证明与该特定令牌绑定。

DPoP 证明的内部结构

DPoP 证明是一个由标头、载荷和签名三部分组成的紧凑 JWT。

// Header - identifies the key and algorithm
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "cWs37kZLJMej6fpd...",
    "y": "e2bkcQGaBERgSZUb..."
  }
}
// Payload - proves freshness and binds to the request
{
  "htm": "POST",
  "htu": "https://auth.example.com/oauth/token",
  "iat": 1774950263,
  "jti": "F1AFCD1F-95F7-401B-A2F5-195A31DB1802",
  "ath": "fUHyO2r2Z3DZ53EsNr..."
}

在 sgcWebSockets 中配置 DPoP

第一步:生成 ES256 密钥对

使用 OpenSSL 生成椭圆曲线密钥对:

# Generate EC private key
openssl ecparam -name prime256v1 -genkey -noout -out dpop_private.pem
# Extract public key parameters
openssl ec -in dpop_private.pem -text -noout

将公钥的 X 和 Y 坐标转换为 Base64URL 以构建 JWK:

{"kty":"EC","crv":"P-256","x":"cWs37kZLJMej6fpdyKaI8Gz6CE...","y":"e2bkcQGaBERgSZUbAGR-iOOM..."}

第二步:配置 OAuth2 客户端

// Configure OAuth2 as usual
OAuth2.OAuth2Options.GrantType := auth2CodePKCE;
OAuth2.OAuth2Options.ClientId := 'your-client-id';
OAuth2.OAuth2Options.ClientSecret := 'your-client-secret';
OAuth2.AuthorizationServerOptions.AuthURL := 'https://auth.example.com/authorize';
OAuth2.AuthorizationServerOptions.TokenURL := 'https://auth.example.com/oauth/token';
// Enable DPoP
OAuth2.DPoPOptions.Enabled := True;
OAuth2.DPoPOptions.Algorithm := dpopES256;
OAuth2.DPoPOptions.PrivateKey.LoadFromFile('dpop_private.pem');
OAuth2.DPoPOptions.PublicKeyJWK := '{"kty":"EC","crv":"P-256","x":"...","y":"..."}';
// Start the OAuth2 flow - DPoP headers are added automatically
OAuth2.Start;

配置完成后,组件将自动:

第三步:在 API 调用中使用 DPoP 证明

获取 DPoP 绑定的访问令牌后,为每次 API 请求生成证明:

var
  vProof: String;
begin
  // Generate a DPoP proof for the API call
  vProof := OAuth2.GetDPoPProof(
    'GET',
    'https://api.example.com/userinfo',
    OAuth2.AccessToken
  );
  // Include both headers in your HTTP request:
  //   Authorization: DPoP <access_token>
  //   DPoP: <proof_jwt>
  HTTPClient.Request.CustomHeaders.AddValue('Authorization',
    'DPoP ' + OAuth2.AccessToken);
  HTTPClient.Request.CustomHeaders.AddValue('DPoP', vProof);
  HTTPClient.Get('https://api.example.com/userinfo');
end;

支持的提供商

DPoP 已获主流 OAuth2 提供商支持:

Auth0 在应用设置中启用 DPoP,需支持 Nonce(已自动处理)。
Okta 在授权服务器访问策略中配置 DPoP,自 2024 年起正式发布。
Microsoft Entra ID 支持机密客户端的 DPoP。
Ping Identity PingOne 和 PingFederate 均完整支持 DPoP。

服务器端 DPoP 验证

sgcWebSockets OAuth2 服务器组件同样支持 DPoP 验证。启用 OAuth2Options.DPoP 后,服务器将自动:

// Enable DPoP on the OAuth2 server
OAuth2Server.OAuth2Options.DPoP := True;
// Optional: custom validation via event
OAuth2Server.OnOAuth2ValidateDPoP := procedure(Sender: TObject;
  Connection: TsgcWSConnection;
  const DPoPProof, AccessToken: String;
  var IsValid: Boolean)
begin
  // Add custom checks here (e.g., verify against a key registry)
  IsValid := True;
end;

DPoP API 参考

属性 / 方法 说明
DPoPOptions.Enabled 为所有令牌请求启用 DPoP。
DPoPOptions.Algorithm dpopES256(推荐)或 dpopRS256
DPoPOptions.PrivateKey 用于签名 DPoP 证明的 PEM 格式私钥。
DPoPOptions.PublicKeyJWK 公钥的 JSON Web Key 表示。
GetDPoPProof() 为指定的 HTTP 方法、URL 和访问令牌生成 DPoP 证明 JWT。
GetDPoPJWKThumbprint() 返回公钥的 RFC 7638 SHA-256 指纹。
DPoPNonce 服务器返回的当前 DPoP-Nonce 值(只读)。

提升令牌安全性

DPoP 是自 PKCE 以来 OAuth 2.0 令牌安全方面最重要的改进。它仅需极少的代码改动即可彻底消除整类令牌盗窃攻击——只需设置 DPoPOptions.Enabled := True,提供密钥,其余由 sgcWebSockets 组件自动处理。

DPoP 支持已在 sgcWebSockets 2026.4.0 中提供,适用于 Delphi(DXE6 至 D13)和 .NET(.NET Framework 2.0 至 .NET 9)。请前往 esegece.com 下载最新版本。