Perl获取微信小程序用户信息(包含openid,unionid)

maweiwei 2017-10-23

涉及微信小程序相关AIP如下:

1、wx.login

2、wx.getUserInfo

3、用户数据的签名验证和加解密

接口wx.getUserInfo当中的 openId 和unionId属于敏感数据,所以接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,就需要对接口返回的加密数据( encryptedData )进行对称解密。 解密算法如下:

  1. 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
  2. 对称解密的目标密文为 Base64_Decode(encryptedData)。
  3. 对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
  4. 对称解密算法初始向量 为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'

}

相关推荐