简体   繁体   English

在 Java 中验证 Paypal Restful Webhook 签名

[英]Verify Paypal Restful Webhook Signature in Java

I have a Java servlet that consumes Paypal Restful Webhooks.我有一个使用 Paypal Restful Webhooks 的 Java servlet。 In verifying the request signature, I followed the instructions detailed in在验证请求签名时,我遵循了详细说明

https://developer.paypal.com/webapps/developer/docs/integration/direct/rest-webhooks-overview/#event-security https://developer.paypal.com/webapps/developer/docs/integration/direct/rest-webhooks-overview/#event-security

However, I can't seem to successfully verify the signature even while following the pseudocode in the paypal doc to the dot.但是,即使按照贝宝文档中的伪代码进行操作,我似乎也无法成功验证签名。 Here's the actual code that I use to verify (the below method always returns false):这是我用来验证的实际代码(下面的方法总是返回 false):

private static final String WEBHOOK_ID = "4HL82785RC0XXXXXX";

private boolean isValidRequest(HttpServletRequest req, String payload) throws Exception {
    String transmissionId = req.getHeader("PAYPAL-TRANSMISSION-ID");
    String timeStamp = req.getHeader("PAYPAL-TRANSMISSION-TIME");
    String crc32 = getCrcSum(payload);

    String expectedSignature = String.format("%s|%s|%s|%s", transmissionId, timeStamp, WEBHOOK_ID, crc32);
    System.out.println("EXPECTED SIG:" + expectedSignature);
    String actualSignatureEncoded = req.getHeader("PAYPAL-TRANSMISSION-SIG");
    String certUrl = req.getHeader("PAYPAL-CERT-URL");

    String algo = req.getHeader("PAYPAL-AUTH-ALGO");
    Signature shaWithRsa = Signature.getInstance(algo);
    byte[] certData = HttpUtils.getBytes(new URL(certUrl), null);

    Certificate certificate = X509Certificate.getInstance(certData);
    shaWithRsa.initVerify(certificate.getPublicKey());
    shaWithRsa.update(expectedSignature.getBytes());

    byte[] actualSignature = Base64.decodeBase64(actualSignatureEncoded.getBytes());

    return shaWithRsa.verify(actualSignature);
}

private static String getCrcSum(final String body) {
    byte[] bytes = body.getBytes();

    CRC32 checkSum = new CRC32();
    checkSum.update(bytes, 0, bytes.length);

    return String.valueOf(checkSum.getValue());
    //return Long.toHexString(checkSum.getValue());
}

HttpUtils.getBytes(new URL(certUrl), null); HttpUtils.getBytes(new URL(certUrl), null); is just a helper method for retrieving the results of a GET request.只是用于检索 GET 请求结果的辅助方法。 It returns a valid Certificate.它返回一个有效的证书。

Probable culprits that I can think of are: 1. Computation of CRC32 is somehow different from how Paypal computes it on their end.我能想到的可能的罪魁祸首是: 1. CRC32 的计算与 Paypal 最终计算它的方式有些不同。 2. Public key from the Paypal URL does not match the Private key used by Paypal. 2. Paypal URL 中的公钥与 Paypal 使用的私钥不匹配。

Here is how I get the payload from the servlet request:以下是我从 servlet 请求中获取有效负载的方法:

String payload = getString(req.getInputStream());


private static String getString(InputStream is) {
    BufferedReader br = null;
    StringBuilder sb = new StringBuilder();

    String line;
    try {

        br = new BufferedReader(new InputStreamReader(is));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    return sb.toString();
}

I use https://developer.paypal.com/developer/webhooksSimulator for testing.我使用https://developer.paypal.com/developer/webhooksSimulator进行测试。

It turns out that the code above is working properly.事实证明,上面的代码工作正常。 The issue was how to properly test it.问题是如何正确测试它。

Paypal's webhook simulator in https://developer.paypal.com/developer/webhooksSimulator is only meant for connectivity tests. https://developer.paypal.com/developer/webhooksSimulator 中的Paypal 网络钩子模拟器仅用于连接测试。

The webhook simulation API detailed in https://developer.paypal.com/webapps/developer/docs/api/#simulate-a-webhook-event will work for verification of the signature, but there is a catch that was NOT documented AFAIK. https://developer.paypal.com/webapps/developer/docs/api/#simulate-a-webhook-event 中详述的 webhook 模拟 API 将用于验证签名,但有一个未记录在案的问题 AFAIK . The simulate API accepts either the webhook_id or the webhook url as a parameter (or both).模拟 API 接受 webhook_id 或 webhook url 作为参数(或两者)。 As per my tests, if you only specify the url, the webhook does not verify properly.根据我的测试,如果您只指定 url,则 webhook 无法正确验证。 But if you specify the webhook_id, the webhook verification process works accordingly.但是,如果您指定 webhook_id,则 webhook 验证过程会相应地工作。

Unfortunately for me, I have been testing using the simulate API while only specifying the url.对我来说不幸的是,我一直在使用模拟 API 进行测试,而只指定了 url。 Thanks to @wpohl for giving me the idea to use the webhook_id in the simulate API.感谢@wpohl 让我想到在模拟 API 中使用 webhook_id。

The documentation stated "Webhook ID: This is the ID of the webhook resource for the destination URL on which the event is delivered".文档说明“Webhook ID:这是传递事件的目标 URL 的 Webhook 资源的 ID”。 So it is not the id of the event (in my case: "WH-36432655JG839693T-2LC486465H4712400").所以它不是事件的 id(在我的例子中:“WH-36432655JG839693T-2LC486465H4712400”)。 But if you use the webhook simulator you didn't have a persistent "webhook resource".但是如果你使用 webhook 模拟器,你就没有持久的“webhook 资源”。

I created an webhook and triggered a notification.我创建了一个 webhook 并触发了一个通知。 and with that ID (In my case "3EB298650W722680T") it works.并使用该 ID(在我的情况下为“3EB298650W722680T”)它可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM