Perl获取微信小程序用户信息(包含openid,unionid)
涉及微信小程序相关AIP如下:
1、wx.login
接口wx.getUserInfo
当中的 openId 和unionId属于敏感数据,所以接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,就需要对接口返回的加密数据( encryptedData )进行对称解密。 解密算法如下:
- 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
- 对称解密的目标密文为 Base64_Decode(encryptedData)。
- 对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
- 对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
微信官方提供了多种编程语言的示例代码(点击下载)。每种语言类型的接口名字均一致。调用方式可以参照示例。
# 前后端流程
1、前端调用接口wx.login ,获得code
2、前端调用接口wx.getUserInfo(参数withCredentials 设为 true),获得加密数据 encryptedData, iv
3、前端将加密数据 encryptedData, iv,以及code传给服务端,服务端按照解密算法,解密数据,得到openid/Unionid返回给前端
# 服务端解密算法(Perl CGI方式)实现如下:
前端将encryptedData, iv,以及code以json方式POST给服务端的CGI脚本,脚本如下:
use warnings;
use utf8;
binmode(STDIN, ':encoding(utf8)');
binmode(STDOUT, ':encoding(utf8)');
binmode(STDERR, ':encoding(utf8)');
use CGI;
use JSON;
use HTTP::Request;
use HTTP::Headers;
use LWP::UserAgent;
use Data::Dumper;
$wechat_config={
appid=>'wxd2317scfhe',
appSecret=>'0b4d05df3f990c8f5576403b1d216d6b'
};
$cgi = CGI->new();
$json = JSON->new;
my $OUTPUT = '{"unionid":"", "openid":"", "status":"failed"}';
#前端POST数据 {"iv":"xxx", "encryptedData":"xxx", "js_code":"xxx", "act":"getUid"}
if ($cgi->param("POSTDATA")) {
my $post_data = $cgi->param("POSTDATA");
#write_log("post_data.log", Dumper($post_data));
#$post_data =~s/[\r\n]//g; #去掉回车换行,以免正则匹配失败
my $input_json = $json->decode($post_data);
if ($input_json->{act} eq "getUid") {
if (length($input_json->{iv}) && length($input_json->{encryptedData}) && length($input_json->{js_code}) ) {
my $obj = $input_json->{obj};
my $iv = $input_json->{iv};
my $data = $input_json->{encryptedData};
my $jscode = $input_json->{js_code};
my $appId = $wechat_config->{appid};
my $key = $wechat_config->{appSecret};
my $ret = get_session_key($appId, $key, $jscode);
my $session_key = "";
if (defined $ret->{session_key}) {
$session_key = $ret->{session_key};
} else {
$OUTPUT = '{"unionid":"", "openid":"", "status":"failed", "errMsg":"get sessionKey failed"}';
print_result($OUTPUT);
exit;
}
#write_log("getUid.log", "jscode=".$jscode."\niv= ".$iv."\nencryptedData= ".$data."\nsession_key=".$session_key);
my $result = readpipe("python /var/app/aes.py $appId $session_key $iv $data");
#write_log("getUid.log", "result= ".$result);
if ($result =~m/{.+}/) {
my $result_json = $json->decode($result);
my $unionid = "";
my $openid = $result_json->{openId};
if (defined $result_json->{unionId}) {
$unionid = $result_json->{unionId};
} else {
$unionid = $result_json->{openId};
}
$OUTPUT = '{"unionid":"'.$unionid.'", "openid":"'.$openid.'", "status":"success"}';
}
}
} else {
write_log("input.log", "act:".$input_json->{act}."Not implement");
}
print_result($OUTPUT);
}
exit;
sub get_session_key {
my ($appId, $secret, $jscode) = @_;
my $session_key_api = "https://api.weixin.qq.com/sns/jscode2session?appid=".$appId."&secret=".$secret."&js_code=".$jscode."&grant_type=authorization_code";
my $ua = LWP::UserAgent->new();
my $req = HTTP::Request->new('GET', $session_key_api);
my $response = $ua->request($req);
my $ret;
if ($response->message ne "OK" && $response->is_success ne "1") { #出错,或者timeout了
$ret->{status} = "time out";
} else {
$ret = $json->decode($response->decoded_content());
}
#write_log("session_key.log", " $session_key_api"."\n wechat.rsp:".Dumper($ret));
return $ret;
}
# 由于解密必须采用AES算法,而Perl实现起来比较复杂,所以采用了readpipe方式调用Python的AES解密算法来实现,即:
my $result = readpipe("python /var/app/aes.py $appId $session_key $iv $data");
# aes.py内容如下:
import base64
#import json
import sys
from Crypto.Cipher import AES
def decryptData(appId, sessionKey, encryptedData, iv):
# base64 decode
sessionKey = base64.b64decode(sessionKey)
encryptedData = base64.b64decode(encryptedData)
iv = base64.b64decode(iv)
cipher = AES.new(sessionKey, AES.MODE_CBC, iv)
decrypted = _unpad(cipher.decrypt(encryptedData))
#decrypted = json.loads(_unpad(cipher.decrypt(encryptedData)))
#if decrypted['watermark']['appid'] != appId:
#print("{\"errMsg\":\"appid mismatched\", \"status\":\"failed\"}")
#sys.exit()
return decrypted
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
if (len(sys.argv) != 5):
print("{\"errMsg\":\"args not enough\", \"status\":\"failed\"}")
sys.exit()
#sys.argv[0] is "aes.py"
appId = sys.argv[1]
sessionKey = sys.argv[2]
iv = sys.argv[3]
encryptedData = sys.argv[4]
decrypted = decryptData(appId, sessionKey, encryptedData, iv)
#print("{\"unionid\":\""+ decrypted['unionId'] + "\", \"status\":\"success\"}")
print(decrypted)
sys.exit()
# aes.py解密后的数据如下:
{
u'province': u'Guangdong', u'openId': u'oGZUI0egBJY1zhBYw2KhdUfwVJJE',
u'language': u'zh_CN', u'city': u'Guangzhou', u'gender': 1,
u'avatarUrl': u'http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0',
u'watermark': {u'timestamp': 1477314187, u'appid': u'wx4f4bc4dec97d474b'},
u'country': u'CN', u'nickName': u'Band', u'unionId': u'ocMvos6NjeKLIBqg5Mr9QjxrP1FA'
}