跳到主要内容

JWTs

本 API 使用 Bearer(令牌)认证 来验证任何请求。这些令牌是 JSON Web Tokens (JWT),需要由您的应用程序在服务器端创建。目前创建 JWT 最简单的方法是使用我们的服务端 SDK,但您也可以在不使用我们 SDK 的情况下,使用任何开源 JWT 库来生成 JWT。

本文档定义了我们 API 的认证规范、JWT 声明和签名机制。

验证 API 调用

每个需要认证的 API 端点都需要在 HTTP 头中包含 authorization 字段,其值为签名的 JWT 令牌(以 bearer 为前缀)。

curl -i -X GET "https://api.efundpay.com/v4/payment" \
-H "authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

创建 API 密钥

要签署 JWT,您需要在控制台中创建一个新的 API 密钥对。

要创建新的 API 密钥,请访问控制台中的 Integrations 面板,然后点击 Add API key 按钮。您可以将 API 密钥与代码一起存储,或将其存储在应用程序可访问的安全环境中。

商户访问权限

选择 API 密钥时,如果您的实例启用了 Multi Merchant,那么您可以选择 API 密钥是否可以访问 All merchants 还是仅访问一个商户。我们建议您在集成到 Embed、我们的 SDK 或电子商务平台(如 Magento、Salesforce Commerce Cloud 和 Commerce Tools)时,将 API 密钥限制为仅访问一个商户帐户。

权限

选择 API 密钥时,您可以选择生成一个可以访问其有权访问的商户内所有 API 的密钥(Full access),或者仅可以访问用于处理支付的 API(Processing only)。我们建议您在使用电子商务平台(如 Magento、Salesforce Commerce Cloud 和 Commerce Tools)时使用 Processing only

通过在 JWT 上设置限制性范围集,可以进一步减少 API 访问权限。

算法

我们的 API 支持两种签署 JWT 的算法:ECDSA 和 RSA。我们建议您使用 ECDSA,除非您的环境不支持这种更强、更新的算法。RSA 算法适用于不支持 ECDSA 的环境。RSA 密钥在相同密钥大小下提供的安全性要低得多,目前我们的 SDK 不支持 RSA 算法。

生成 JWT

您可能不想使用我们的 SDK,或者您的语言可能没有可用的 SDK。在这些情况下,您可以使用 jwt.io 上提供的众多库之一来构建和签署 JWT。

在高层次上,JWT 由 3 个部分组成:

  • 定义用于创建 JWT 的算法和密钥的 header(头部)。
  • 定义令牌范围和其他权限的一组 claims(声明)。
  • 基于头部和声明的加密 signature(签名),使用您的私钥签署。

结合这 3 个部分构成 JSON Web Token (JWT)。有关规范和生成 JWT 的可用库的更多详细信息,请参见 jwt.io。

JWT 头部

JWT 头部定义加密算法类型以及用于生成签名的私钥。

{
"typ": "JWT",
"alg": "ES512", // 或 RS512,取决于您的密钥
"kid": "d757c76acbd74b56"
}

typalg 是固定的,不允许其他值。kid 是您私钥的 ID,您可以在控制台的 Integrations 面板中找到它。

JWT 声明

声明定义了令牌的创建时间和访问权限。

{
"iss": "My JWT Generation Tool",
"nbf": 1607976645,
"exp": 1607977245,
"jti": "0fe1fb1b-2f7e-4c8d-b0eb-aae5d0ec98f7",
"scopes": ["transactions.read"],
"embed": {
"amount": "200",
"currency": "AUD",
"buyer_id": "d757c76a-cbd7-4b56-95a3-40125b51b29c",
"metadata": { "key": "value" },
"cart_items": [
{
"name": "Joust Duffle Bag",
"quantity": "1",
"unit_amount": "9000",
"tax_amount": "0"
}
]
}
}

声明字段

API 支持以下 JWT 声明。

字段名描述是否必需
iss代表您的代码进行此调用的唯一 ID。这有助于识别哪个库向 EFundFlow 发出了 API 调用。
nbf此令牌创建时的 UNIX 时间戳(以秒为单位)。
exp此令牌过期时的 UNIX 时间戳(以秒为单位)。
iat可选的 UNIX 时间戳(以秒为单位),供您内部使用以指示令牌的签发时间。
jti用于加密熵的随机唯一 ID。每个 JWT 都需要唯一。
scopes授予此令牌访问 API 权限的范围列表。
embed用于在 Embed 中固定金额、货币和买家信息的键值对字典。
checkout_session_id结账会话的 ID。这可以用来将多个交易绑定在一起,表示它们来自同一个会话。
merchantId商户唯一标识。用于指定该 JWT 颁发给哪个商户。

时间戳

请注意,nbfexpiat 值是 UNIX 时间戳,定义为自 1970 年 1 月 1 日(UTC)以来的秒数。某些编程语言返回的 UNIX 时间戳是毫秒,需要删除最后 3 位数字。

范围

API 支持 scopes 声明的以下值。

范围描述
*.read允许对任何资源进行读取访问。这是 SDK 中的默认设置
*.write允许对任何资源进行写入访问。这是 SDK 中的默认设置。这不包括读取访问权限。
{resource_name}.read允许对某类型或资源进行读取访问。例如,payment-services.read 启用对买家数据的读取访问。
{resource_name}.write允许对某类型或资源进行写入访问。例如,payment-services.write 启用对买家数据的写入访问。这不包括读取访问权限。
embed代表 Embed 所需的所有访问权限的范围。

识别以下资源名称。有关每个端点需要什么范围的更多详细信息,请参阅参考文档。

  • anti-fraud-services
  • api-logs
  • buyers
  • buyers.billing-details
  • card-scheme-definitions
  • checkout-sessions
  • connections
  • digital-wallets
  • flows
  • payment-methods
  • payment-method-definitions
  • payment-options
  • payment-service-definitions
  • payment-services
  • reports
  • transactions

签名和组装

最后,JWT 签名是通过连接 Base64 编码的头部和声明(用 . 分隔)并通过密钥算法运行它来生成的。

ECDSA

ECDSASHA512(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
public_key,
private_key
)

组装后的 JWT 通过连接 Base64 编码的头部、声明和签名(用句号分隔)形成。

base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)

Java 代码示例

  import java.security.PrivateKey;
import java.util.*;
import java.time.Instant;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory;

private static String createValidJwtToken() {
try {
String TEST_RSA_PRIVATE_KEY_BASE64 = "MIIEvg....UGu5Rp8";
String alg = "RS512";
String header = String.format("{\"typ\":\"JWT\",\"alg\":\"%s\",\"kid\":\"%s\"}", alg, TEST_SUB_MERCHANT_ID);
String encodedHeader = base64UrlEncode(header);

long currentTime = Instant.now().getEpochSecond();
String payload = String.format(
"{\"iss\":\"efundflow.com\",\"nbf\":%d,\"exp\":%d,\"jti\":\"test-jwt-id\",\"scopes\":[\"transactions.read\"]}",
currentTime - 60, currentTime + 3600
);
String encodedPayload = base64UrlEncode(payload);;
PrivateKey privateKey = loadRSAPrivateKey(TEST_RSA_PRIVATE_KEY_BASE64);
String signature = generateRS512Signature(header, payload, privateKey);
return encodedHeader + "." + encodedPayload + "." + signature;
} catch (Exception e) {
LOGGER.error("Failed to create test JWT token", e);
return "invalid.token.for.testing";
}
}

private static PrivateKey loadRSAPrivateKey(String base64Key) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}

private static String generateRS512Signature(String header, String payload, PrivateKey privateKey) throws Exception {
String data = base64UrlEncode(header) + "." + base64UrlEncode(payload);
Signature signature = Signature.getInstance("SHA512withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return base64UrlEncode(signBytes);
}

private static String base64UrlEncode(String str) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(str.getBytes());
}


Powered by Docusaurus