使用 Spring Boot 进行 Apple Pay 商户验证(响应式)

问题描述 投票:0回答:2

我们有一个 Spring Boot 后端,尝试执行商家验证并检索 Apple Pay 的支付会话。按照 Apple 的这些步骤,我们能够生成商家验证证书。当我尝试以编程方式创建密钥库并添加 Apple 颁发的证书以在我的 Web 客户端调用中使用时,出现以下错误:

Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

这是代码片段:

val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())

val certificateFactory = CertificateFactory.getInstance("X.509")

val keyStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)

val merchantValidationInputStream = FileInputStream(<PATH_TO_CERT_FROM_APPLE>)
val merchantValidationCert= certificateFactory.generateCertificate(merchantValidationInputStream)
keyStore.setCertificateEntry("applePayMerchantValidationCert", merchantValidationCert)

val sslContext = SslContextBuilder
                            .forClient()
                            .trustManager(trustManagerFactory)
                            .build()

val httpClient =   HttpClient.create()
                .secure { it.sslContext(sslContext) }

val webClient = builder
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .baseUrl(<VALIDATION_URL_PASSED_FROM_WEB>)
    .build()

return webClient.post()
    .bodyValue(<REQUEST_BODY_WITH_MERCHANTID_DISPLAYNAME_INITIATIVE_INITIATIVE_CONTEXT>)
    .retrieve()
    .toBodilessEntity()
// For now I am just trying to get this call working and not doing anything with the response. 
// Using https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session#3199965 
// as reference for the request body structure

我可以获得一些帮助来弄清楚这里可能发生什么吗?

工具/框架版本

  • 春季启动:2.7.6
  • 科特林:1.7.22
  • Java:17(采用)

我尝试过的事情:

  • 使用 keytool 从 Apple 导入根证书和中间证书(将它们添加为可信证书)
  • 尝试将 Apple 的 .cer 文件转换为 .pem 文件 在将其添加到网络客户端调用之前
spring-boot jvm certificate spring-webflux applepay
2个回答
3
投票

与@janani-subbiah 合作后,我们确定以下是解决方案。

有关完整说明,请访问 Apple Pay 文档,但我想对我们需要进行的证书转换以及使用它的 Spring Boot 代码进行高级概述。另请注意,您可以使用 OpenSSL 进行大部分证书更改,但为了简单起见,我只是使用 Mac 钥匙串工具。

  1. 在Apple控制台创建商户ID
  2. 使用 ECC 和 256 位密钥对在 Mac 钥匙串应用程序上创建 CSR
  3. 使用来自的 CSR 在 Apple Console 中创建支付处理证书 上一步(这被发送到我们的支付处理器,不用于 我们的代码)
  4. 添加将使用此 Apple Pay 配置的所有前端域 苹果控制台
  5. 下载每个域的Apple验证文件并部署它 到Apple在前端指定的路径以完成验证
  6. 使用默认设置在 Mac 钥匙串访问应用程序上创建 CSR
  7. 在 Apple Console 中创建商户身份证书(这是 需要通过我们的后端发送用于双向 TLS 的证书)
  8. 下载该证书,在您在步骤 6 中使用的同一台 Mac 上的“钥匙串访问”中打开它,然后转到您的 登录钥匙串,选择“我的证书”。你应该看到 那里有商家证书,可以扩展以查看私钥。 选择证书和私钥,然后右键单击并选择 导出 2 项。在导出中确保选择 p12 格式并设置 后端将使用的安全密码。
  9. 最后,在 Spring Boot 应用程序中,您需要以下内容才能使用证书。
data class PaymentSessionRequest(
    val merchantIdentifier: String = "merchant.your merchant identifier",
    val displayName: String = "Any Apple Pay Display Name",
    val initiative: String = "web",
    val initiativeContext: String = "Fully qualified domain of the frontend"
)

val pass = "Password you set when exporting the p12 cert in step 8" // Should be set as a secret and exposed as an environment variable.
val inputStream = javaClass.getResourceAsStream("/merchant_id.p12") // Should be mounted as a secret file and loaded from there.
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(inputStream, pass.toCharArray())

val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, pass.toCharArray())

val context = SslContextBuilder.forClient()
    .keyManager(keyManagerFactory)
    .build()

val client: HttpClient = HttpClient.create().secure { spec -> spec.sslContext(context) }
val connector: ClientHttpConnector = ReactorClientHttpConnector(client)

val webClient = WebClient.builder()
    .baseUrl("https://apple-pay-gateway.apple.com") // Should use validation URL passed in from the Apple Pay JS SDK and include IP address validation based on Apple's docs
    .clientConnector(connector)
    .build()

webClient.post()
    .uri("/paymentservices/paymentSession")
    .bodyValue(PaymentSessionRequest())
    .retrieve()
    .bodyToMono<String>()
    .doOnNext { logger.info("Apple Pay Session response: $it") }

-1
投票

尝试使用 OpenSSL 将证书文件从 Apple (.cer) 转换为 PEM 格式来执行此转换。

外壳 openssl x509 -inform der -in your_certificate.cer -out your_certificate.pem

创建Java KeyStore并将转换后的PEM证书导入其中。

Java 代码 导入 java.io.FileInputStream; 导入 java.security.KeyStore; 导入 java.security.cert.Certificate;

// ...

String keyStorePath = "path/to/your/keystore.jks";
char[] keyStorePassword = "your_keystore_password".toCharArray();
String certificatePath = "path/to/your_certificate.pem";
String alias = "your_certificate_alias";

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, keyStorePassword);

FileInputStream certificateFileInputStream = new FileInputStream(certificatePath);
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(certificateFileInputStream);
keyStore.setCertificateEntry(alias, certificate);
keyStore.store(new FileOutputStream(keyStorePath), keyStorePassword);

配置您的 SSLContext 以使用 KeyStore。 导入 javax.net.ssl.KeyManagerFactory; 导入 javax.net.ssl.SSLContext;

// ...

String keyStoreType = "JKS";

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

在 HttpClient 中使用配置的 SSLContext。 导入 org.apache.http.conn.ssl.SSLContextBuilder; 导入 org.apache.http.impl.client.CloseableHttpClient; 导入 org.apache.http.impl.client.HttpClients;

// ...

CloseableHttpClient httpClient = HttpClients.custom()
    .setSSLContext(sslContext)
    .build();

这应该可以解决

SSLHandshakeException
问题。

此外,请将我上面给出的示例中的占位符值 (

your_certificate.cer, your_certificate.pem, path/to/your/keystore.jks, your_keystore_password, and your_certificate_alias
) 替换为您应用程序中的值。

© www.soinside.com 2019 - 2024. All rights reserved.