支付宝小程序一键(快捷)登录功能实现原理及验签、解密过程遇到的问题
支付宝小程序不知什么时候有了「获取会员手机号」的功能,很明显对标的是微信小程序。小程序中的一键登录就是通过获取当前会员手机号来实现快捷登录的。手机号属于会员敏感信息,所以在小程序端拿到的是加密的会员信息,需要后端去做验签和解密,以此获取手机号并实现会员一键登录。
在写代码之前,需要做的几件事:
- 支付宝小程序后台添加「获取会员手机号」能力,添加后还需要提交一个申请表单,申请通过之后前端才可以调起
- 获取支付宝公钥以实现验签功能,根据具体「接口加签方式」不同,需要支付宝公钥或者支付宝公钥证书
- 获取 AES 密钥以实现解密功能,在「接口内容加密方式」设置并获取。
小程序端通过调起授权获取到 response
和 sign
两个参数,其中 response
既是加密密文,也是签名的报文内容。
以下是支付宝 PHP 同步返回验签示例:
$aop = new AopClient ();
//编码格式
$aop->postCharset="UTF-8";
//支付宝公钥赋值
$aop->alipayrsaPublicKey="";
//响应的待验签字符串
$_POST="{\"code\":\"10000\",\"msg\":\"Success\",\"app_id\":\"20****75\",\"auth_app_id\":\"2014****5\",\"charset\":\"utf-8\",\"timestamp\":\"2019-04-01 14:33:01\",\"out_trade_no\":\"0401022927-9449\",\"total_amount\":\"0.01\",\"trade_no\":\"201904012200145675**39\",\"seller_id\":\"20****5\"}";
//sign值
$sign="";
//签名方式
$sign_type="RSA2";
//验签代码
$flag = $aop->verify($_POST, $sign,null,$sign_type);
if ($flag)
{
echo "success";
}
else
{
echo "fail";
}
AopClient
是 PHP 版本下支付宝 sdk 中的一个主要类,里面包含了加签、验签和加解密基本的基本方法。
解密示例代码如下:
根据服务器 PHP 版本,我选择了 PHP 版本 7.0 以上的 demo。
按照 敏感信息加解密方法 中的交互流程图,服务端需要完成三个步骤的逻辑:
- 验证请求的合理性(包括登录态校验)
- 报文验签(验签失败则流程终止)
- 数据解密(解密失败则流程终止),存储
问题主要出在第二个步骤:报文校验。上面的「支付宝 PHP 同步返回验签示例」怎么改都通过不了。而略过验签步骤,直接解密是可以拿到手机号数据的。
示例代码比较可疑,尤其是那个「响应的待验签字符串」与实际拿到的 response
字符串完全没有可比性。感觉这个示例代码应该是支付回调的示例。怀疑过公钥,也将 $aop->verify()
方法拆解开来本地测试,仍然无法通过。
让做 Java 的同事拿示例代码尝试验签,没有任何问题,一次通过。这说明验签需要的参数都没有问题。
对照 Java 的示例,找到了一处不同点:
// 是否加密
boolean isDataEncrypted = !content.startsWith("{");
boolean signCheckPass = false;
//2. 验签
String signContent = content;
String signVeriKey = "你的小程序对应的支付宝公钥(为扩展考虑建议用appId+signType做密钥存储隔离)";
String decryptKey = "你的小程序对应的加解密密钥(为扩展考虑建议用appId+encryptType做密钥存储隔离)"
;
// 如果是加密的报文则需要在密文的前后添加双引号
if (isDataEncrypted) {
signContent = "\"" + signContent + "\"";
}
Java 版本对报文内容做了处理。因为原始报文就是一串 base64 字符串,所以代码中将报文前后添加双引号作为验签的报文,解密用的还是原来的报文。
加上双引号,验签通过。
回顾整个过程,在一开始我就注意到了 Java 代码的特殊处理,但还是盲目相信了 PHP 的示例代码,自己还觉得报文本来就是字符串,无需再加双引号。可笑可笑。
从整个项目过程中对支付宝的对接也暴露出一个问题,那就是别人写的示例,只是示例。大体上要相信文档,而局部出现问题时要有怀疑精神,要参考 Java 等其他版本做一个对照,这样可以快速找到问题所在。
参考文档:
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。