Webhook 订单回调验签

本文是 Webhook 订单回调验签 的 demo

使用本文代码时,请务必将其中的合作伙伴账号相关信息替换为您自身的

package onramp.webhookvalidation;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * SignUtil
 *
 * @author FaTPay
 */
public class SignUtil {

    private static final Logger LOG = LoggerFactory.getLogger(SignUtil.class);

    /**
     * Signature algorithms
     */
    public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    /**
     * Webhook public key
     */
    private static final String FATPAY_PUBLIC_KEY = "<FaTPay RSA PUBLIC KEY>";

    /**
     * Verifies the signature
     *
     * @param params
     * @param rsaPublic
     * @return
     */
    public static Boolean rsaVerify(Map<String, String> params, String rsaPublic, String presign, String sign) {
        try {
            rsaPublic = StringUtils.replace(rsaPublic, "-----BEGIN PRIVATE KEY-----", "");
            rsaPublic = StringUtils.replace(rsaPublic, "-----END PRIVATE KEY-----", "");
            return doVerify(presign + paramsToStr(params), sign, rsaPublic);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Verifies the signature
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     */
    public static boolean doVerify(String content, String sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(pubKey);
            signature.update(content.getBytes(StandardCharsets.UTF_8));

            return signature.verify(Base64.decodeBase64(sign));
        } catch (Exception ex) {
            LOG.error("验签失败,content={}", content, ex);
            throw new IllegalArgumentException("rsa verify fail");
        }
    }

    /**
     * Converts parameters into ordered strings
     *
     * @param params
     * @return
     */
    private static String paramsToStr(Map<String, String> params) {
        StringBuilder param = new StringBuilder();
        List<String> keys = new ArrayList(params.keySet());
        // Sorts the specified keys
        Collections.sort(keys);
        int index = 0;
        Iterator<String> iterator = keys.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            String value = params.get(key);
            // Filter out null and empty values
            if ("x-fp-signature".equals(key) || "signature".equals(key) || "sign".equals(key) || StringUtils.isBlank(value)) {
                continue;
            }
            param.append(index == 0 ? "" : "&").append(key).append("=").append(value);
            index++;
        }
        return param.toString();
    }

    /**
     * Returns URI
     *
     * @param url
     * @return
     */
    public static URI getURI(String url) {
        if (!(StringUtils.startsWithIgnoreCase(url, "http://") || StringUtils
                .startsWithIgnoreCase(url, "https://"))) {
            url = "http://" + url;
        }
        try {
            URI uri = new URI(url);
            return uri;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    // ******************** WEBHOOK DEMO START ********************//
    @PostMapping("/webhook_sign_verify")
    public String webhookSignVerify(@RequestBody Map<String, String> requestBody, HttpServletRequest request) {
        String signature = request.getHeader("X-FP-Signature");

        // Converts header keys to lower case
        Map<String, String> headers = new HashMap<>();
        headers.put("x-fp-timestamp", request.getHeader("X-FP-Timestamp"));
        headers.put("x-fp-nonce", request.getHeader("X-FP-Nonce"));
        headers.put("x-fp-partner-id", request.getHeader("X-FP-Partner-Id"));

        Map<String, String> params = new HashMap<>();
        params.putAll(requestBody);
        params.putAll(headers);

        String url = request.getRequestURL().toString();
        URI uri = SignUtil.getURI(url);
        String presign = request.getMethod() + uri.getHost() + request.getRequestURI() + "?";
        Boolean verified = false;
        try {
            verified = SignUtil.rsaVerify(params, FATPAY_PUBLIC_KEY, presign, signature);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (!verified) {
            return "Signature error";
        }
        // do something
        return "success";
    }
    // ******************** WEBHOOK DEMO END ********************//
}

Last updated