Sendgrid Webhook签名验证始终失败,请求技术协助
SendGrid Webhook签名验证始终返回false的排查与解决
可能的问题点及对应解决方案
1. 请求体不一致(最常见原因)
Spring的@RequestBody String rawData可能会自动对请求体做转义、编码转换或格式调整,导致和SendGrid发送的原始payload不一致。
解决方法:直接读取原始请求流,避免Spring的自动处理:
@PostMapping("/sendgrid-callback") public boolean acceptSendgridCallback(HttpServletRequest request) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, SignatureException, InvalidKeyException { String timestamp = request.getHeader("X-Twilio-Email-Event-Webhook-Timestamp"); String signature = request.getHeader("X-Twilio-Email-Event-Webhook-Signature"); // 读取原始UTF-8编码的请求体 String rawData = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) .lines() .collect(Collectors.joining("\n")); System.out.println("Raw payload: " + rawData); ECPublicKey ecdsaKey = eventWebhook.ConvertPublicKeyToECDSA("your-sendgrid-public-key"); boolean b = eventWebhook.VerifySignature(ecdsaKey, rawData.getBytes(StandardCharsets.UTF_8), signature, timestamp); return b; }
同时对比webhook.site收到的原始payload和控制器打印的rawData,确保完全一致(包括空格、换行、转义符、编码)。
2. 公钥转换逻辑错误
SendGrid提供的公钥是PEM格式,如果你的ConvertPublicKeyToECDSA方法没有正确处理格式,会导致公钥无效。
正确的公钥转换代码:
public ECPublicKey ConvertPublicKeyToECDSA(String publicKeyPem) throws NoSuchAlgorithmException, InvalidKeySpecException { // 清理PEM格式的页眉、页脚和换行符 String cleanedPem = publicKeyPem.replaceAll("-----BEGIN PUBLIC KEY-----", "") .replaceAll("-----END PUBLIC KEY-----", "") .replaceAll("\\s+", ""); byte[] keyBytes = Base64.getDecoder().decode(cleanedPem); KeyFactory keyFactory = KeyFactory.getInstance("EC"); return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes)); }
3. 测试流程中payload被篡改
从webhook.site导出Curl再导入Postman的过程中,可能出现payload格式被修改(比如自动URL编码、转义字符变化)。
解决方法:
- 直接复制webhook.site中的原始payload文本,手动粘贴到Postman的请求体中
- 确保请求头的
X-Twilio-Email-Event-Webhook-Timestamp和X-Twilio-Email-Event-Webhook-Signature完全和webhook.site收到的一致,不要手动修改
4. 编码不一致导致字节数组错误
SendGrid生成签名时使用UTF-8编码,你的代码中如果默认用了系统编码,会导致字节数组不匹配。
修改签名验证代码,明确指定UTF-8:
public boolean VerifySignature(ECPublicKey publicKey, byte[] payload, String signature, String timestamp) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException { final ByteArrayOutputStream payloadWithTimestamp = new ByteArrayOutputStream(); // 明确使用UTF-8编码 payloadWithTimestamp.write(timestamp.getBytes(StandardCharsets.UTF_8)); payloadWithTimestamp.write(payload); final Signature signatureObject = Signature.getInstance("SHA256withECDSA", "BC"); signatureObject.initVerify(publicKey); signatureObject.update(payloadWithTimestamp.toByteArray()); final byte[] signatureInBytes = Base64.getDecoder().decode(signature); return signatureObject.verify(signatureInBytes); }
5. BouncyCastle提供者未正确配置
如果项目中没有引入BouncyCastle依赖或未注册提供者,会导致签名算法初始化失败。
Maven依赖添加:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
代码中注册提供者:
// 在应用启动时添加 Security.addProvider(new BouncyCastleProvider());
内容的提问来源于stack exchange,提问作者vigamage
相关产品推荐
相关产品推荐

