欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-04 18:07:34
...

涉及微信小程序相关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'

 

}