77 */
88package com .pingxx .example ;
99
10- import java .io .*;
11- import java .security .InvalidKeyException ;
12- import java .security .KeyFactory ;
13- import java .security .NoSuchAlgorithmException ;
14- import java .security .PublicKey ;
15- import java .security .Signature ;
16- import java .security .SignatureException ;
17- import java .security .spec .X509EncodedKeySpec ;
18-
19- import org .apache .commons .codec .binary .Base64 ;
10+ import com .pingplusplus .Pingpp ;
11+ import com .pingplusplus .net .APIResource ;
12+ import com .pingplusplus .util .PingppSignature ;
2013
2114/**
22- * Created by sunkai on 15/5/19. webhooks 验证签名示例
23- *
15+ * webhooks 验证签名示例
16+ * <p>
2417 * 该实例演示如何对 Ping++ webhooks 通知进行验证。
2518 * 验证是为了让开发者确认该通知来自 Ping++ ,防止恶意伪造通知。用户如果有别的验证机制,可以不进行验证签名。
26- *
19+ * <p>
2720 * 验证签名需要 签名、公钥、验证信息,该实例采用文件存储方式进行演示。
2821 * 实际项目中,需要用户从异步通知的 HTTP header 中读取签名,从 HTTP body 中读取验证信息。公钥的存储方式也需要用户自行设定。
29- *
30- * 该实例仅供演示如何验证签名,请务必不要直接 copy 到实际项目中使用。
31- *
22+ * <p>
23+ * 该实例仅供演示如何验证签名,请务必不要直接 copy 到实际项目中使用。
3224 */
3325public class WebhooksVerifyExample {
34-
35- private static String pubKeyPath = "res/pingpp_public_key.pem" ;
36- private static String eventPath = "res/webhooks_raw_post_data.json" ;
37- private static String signPath = "res/signature.txt" ;
38-
3926 /**
4027 * 验证 webhooks 签名,仅供参考
28+ *
4129 * @param args
4230 * @throws Exception
4331 */
@@ -46,78 +34,17 @@ public static void main(String[] args) throws Exception {
4634 }
4735
4836 public static void runDemos () throws Exception {
37+ String verifyPublicKey = Pingpp .verifyPublicKey ;
4938 // 该数据请从 request 中获取原始 POST 请求数据, 以下仅作为示例
50- String webhooksRawPostData = getStringFromFile (eventPath );
39+ String webhooksRawPostData = "{\" id\" :\" evt_400240816160755820469807\" ,\" created\" :1723795675,\" livemode\" :false,\" type\" :\" charge.succeeded\" ,\" data\" :{\" object\" :{\" id\" :\" ch_100240816592822384640019\" ,\" object\" :\" charge\" ,\" created\" :1723795492,\" livemode\" :false,\" paid\" :true,\" refunded\" :false,\" reversed\" :false,\" app\" :\" app_eTC8yLuj9GSSL0yv\" ,\" channel\" :\" wx_pub\" ,\" order_no\" :\" 1723795491985uqclqj7\" ,\" client_ip\" :\" 127.0.0.1\" ,\" amount\" :100,\" amount_settle\" :100,\" currency\" :\" cny\" ,\" subject\" :\" Your Subject\" ,\" body\" :\" Your Body\" ,\" extra\" :{\" open_id\" :\" o7xEMsySBFG3MVHI-9VsAJX-j50W\" ,\" limit_pay\" :\" no_credit\" ,\" bank_type\" :\" your bank type\" },\" time_paid\" :1723795675,\" time_expire\" :1723802692,\" time_settle\" :null,\" transaction_no\" :\" 1290362656202408163243920572\" ,\" refunds\" :{\" object\" :\" list\" ,\" url\" :\" /v1/charges/ch_100240816592822384640019/refunds\" ,\" has_more\" :false,\" data\" :[]},\" amount_refunded\" :0,\" failure_code\" :null,\" failure_msg\" :null,\" metadata\" :{},\" credential\" :{},\" description\" :null}},\" object\" :\" event\" ,\" request\" :\" iar_mrrvj9ivPOaHPOi9aLPWPqvT\" ,\" pending_webhooks\" :0}" ;
5140 System .out .println ("------- POST 原始数据 -------" );
5241 System .out .println (webhooksRawPostData );
5342 // 签名数据请从 request 的 header 中获取, key 为 X-Pingplusplus-Signature (请忽略大小写, 建议自己做格式化)
54- String signature = getStringFromFile ( signPath ) ;
43+ String signature = "Ju5ItDM9Hblkm6Pbb/r3G9iM58oCtAUJlJDrOud0E23TUtGBxKYJSMcuTFbPgLGLKy5QHYovJJojjMjM1CMGCp82F+SOY1U2zuwzkAk0lVqhfQk+CWGpcyUzOtLOOblKidyuIb+axZDTIg4kK/JoOR2xMH3+cJD9BN6rtfzEDbHqyZIfv6n3y/LdZNhsXfQq+qoIRuLdHrFexk1USgk7SXFvH1pCOIt8o2+dryp/ixlNj1vGq57eE8sbEzPo72vxLtzPazlgR+67cS1bUxjzObh7YFbSSTJxkHDEIi3ipTpoiI7Kc5ng7EzsWgp3cAl7on5HztfFLfT8+Bjpdvtr6w==" ;
5544 System .out .println ("------- 签名 -------" );
5645 System .out .println (signature );
5746
58- boolean result = verifyData ( webhooksRawPostData , signature , getPubKey ());
47+ boolean result = PingppSignature . verify ( signature , webhooksRawPostData , verifyPublicKey , APIResource . CHARSET . name ());
5948 System .out .println ("验签结果:" + (result ? "通过" : "失败" ));
6049 }
61-
62- /**
63- * 读取文件, 部署 web 程序的时候, 签名和验签内容需要从 request 中获得
64- * @param filePath
65- * @return
66- * @throws Exception
67- */
68- public static String getStringFromFile (String filePath ) throws Exception {
69- FileInputStream in = new FileInputStream (Main .projectDir + filePath );
70- InputStreamReader inReader = new InputStreamReader (in , "UTF-8" );
71- BufferedReader bf = new BufferedReader (inReader );
72- StringBuilder sb = new StringBuilder ();
73- String line ;
74- do {
75- line = bf .readLine ();
76- if (line != null ) {
77- if (sb .length () != 0 ) {
78- sb .append ("\n " );
79- }
80- sb .append (line );
81- }
82- } while (line != null );
83-
84- return sb .toString ();
85- }
86-
87- /**
88- * 获得公钥
89- * @return
90- * @throws Exception
91- */
92- public static PublicKey getPubKey () throws Exception {
93- String pubKeyString = getStringFromFile (pubKeyPath );
94- pubKeyString = pubKeyString .replaceAll ("(-+BEGIN PUBLIC KEY-+\\ r?\\ n|-+END PUBLIC KEY-+\\ r?\\ n?)" , "" );
95- byte [] keyBytes = Base64 .decodeBase64 (pubKeyString .getBytes ("UTF-8" ));
96-
97- // generate public key
98- X509EncodedKeySpec spec = new X509EncodedKeySpec (keyBytes );
99- KeyFactory keyFactory = KeyFactory .getInstance ("RSA" );
100-
101- return keyFactory .generatePublic (spec );
102- }
103-
104- /**
105- * 验证签名
106- * @param dataString
107- * @param signatureString
108- * @param publicKey
109- * @return
110- * @throws NoSuchAlgorithmException
111- * @throws InvalidKeyException
112- * @throws SignatureException
113- */
114- public static boolean verifyData (String dataString , String signatureString , PublicKey publicKey )
115- throws NoSuchAlgorithmException , InvalidKeyException , SignatureException , UnsupportedEncodingException {
116- byte [] signatureBytes = Base64 .decodeBase64 (signatureString );
117- Signature signature = Signature .getInstance ("SHA256withRSA" );
118- signature .initVerify (publicKey );
119- signature .update (dataString .getBytes ("UTF-8" ));
120- return signature .verify (signatureBytes );
121- }
122-
12350}
0 commit comments