在使用php作为服务端语言获取微信小程序用户信息和完成登录逻辑时,我们先来分析下官网给出的登录流程图。

image.png


看图,我们把流程大概分为以下几个步骤

1、小程序(也就是前端)调用wx.login()方法获取code

2、调用wx.request()方法发起http请求将code发送到开发者服务器(也就是我们的服务端)

3、服务端通过curl发送appid+secret+code 到微信接口获取openid,session_key

微信接口地址为:

1
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

4、服务端自定义登录状态(简单来说就是给这个用户绑定一个token,比如key=>value方式存入redis)。其实在这一步之前有必要的话是需要获取用户信息,将用户信息保存到数据库中(如:用户昵称,头像,性别)等,这也是我们今天这文章的主题。

5、返回登录状态(返回token)

6、接下来后面的请求都携带token 以备验证该用户和返回数据

如何获取用户信息?

image.png

数据签名校验

为了确保 开放接口 返回用户数据的安全性,微信会对明文数据进行签名。开发者可以根据业务需要对数据包进行签名校验,确保数据的完整性。

  1. 通过调用接口(如 wx.getUserInfo)获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )

  2. 开发者将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 即可校验数据的完整性。

通过前台传入的签名信息进行延签,解密之后获得微信用户信息具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public function login()
{
    try{
        ifempty($this->params['code']) ||
            empty($this->params['rawData']) ||
            empty($this->params['encryptedData']) ||
            empty($this->params['iv']) ||
            empty($this->params['signature'])
        ){
            throw new \Exception('参数错误',-1);
        }
        //根据code 获取openid和session_key
        $appid  = Config::get('wechat.appid');
        $secret = Config::get('wechat.secret');
        //curl请求获得openid,session_key
        $curl   =  "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$this->params['code']}&grant_type=authorization_code";
        $res    = \app\server\Http::get_request($curl); 
        $res    = json_decode($res,true);
        if(empty($res['openid']) || empty($res['session_key'])){
            throw new \Exception('获取用户信息失败',-1);
        }
        //验证签名
        $signature = sha1($this->params['rawData'].$res['session_key']);
        if($this->params['signature'] != $signature){
            throw new \Exception('数字签名失败',-1);
        }
        //调用解密方法获得用户信息
        $pc new \app\server\WxBizDataCrypt($appid,$res['session_key']);
        $errCode $pc->decryptData($this->params['encryptedData'], $this->params['iv'], $data );
        if ($errCode != 0) {
            throw new \Exception($errCode);
        }
        $user $this->wechat_user->where(['openid' => $res['openid']])->find();
        //用户信息入库
        if(!empty($user)){
            $this->wechat_user->where(['openid' => $res['openid']])->update([
                'session_key' => $res['session_key'],
                'nickname'    => $data['nickName'],
                'gender'      => $data['gender'],
                'city'        => $data['city'],
                'avatar_url'  => $data['avatarUrl'],
                'login_time'  => date('Y-m-d H:i:s'),
            ]);
            $wid $user['wid'];
        }else{
            $wid $this->wechat_user->insert([
                'openid'      => $res['openid'],
                'session_key' => $res['session_key'],
                'nickname'    => $data['nickName'],
                'gender'      => $data['gender'],
                'city'        => $data['city'],
                'avatar_url'  => $data['avatarUrl'],
                'login_time'  => date('Y-m-d H:i:s'),
            ]);
        }
        //保持用户登录状态
        \app\server\Redis::get_instence()->set($signature,$wid,3600*24*15);
    }catch (\Exception $exception){
        return _error($exception->getMessage(),$exception->getCode());
    }
    return json([
        'token'        => $signature,
        'login_status' => 1,
        'login_time'   => date('Y-m-d H:i:s')
    ]);
}

上面方法中有用到2个方法:一个是

\app\server\Http::get_request($curl);

具体代码如下

public static function get_request($url)
{
   //初始化
   $curl = curl_init();
   //设置抓取的url
   curl_setopt($curl, CURLOPT_URL, $url);
   //设置头文件的信息作为数据流输出
   curl_setopt($curl, CURLOPT_HEADER, false); //返回response头部信息
   //设置获取的信息以文件流的形式返回,而不是直接输出。
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
   //执行命令
   $data = curl_exec($curl);
   //关闭URL请求
   curl_close($curl);
   //显示获得的数据
   return $data;
}

一个是wxBizDataCrypt类我放在 app\server下,代码如下

<?php
/**
* User: tangyijun
* Date: 2018-08-14
* Time: 11:47
*/
namespace app\server;

/**
* Class WxBizDataCrypt
* @package app\server
* 微信小程序解密
*/
class WxBizDataCrypt{
   /**
    * @var int
    * 定义错误码
    */
   public static $OK                = 0;
   public static $IllegalAesKey     = -41001;
   public static $IllegalIv         = -41002;
   public static $IllegalBuffer     = -41003;
   public static $DecodeBase64Error = -41004;
   private $appid;
   private $sessionKey;
   public function __construct($appid,$session_key)
   {
       $this->sessionKey = $session_key;
       $this->appid = $appid;
   }


   /**
    * @param $encryptedData
    * @param $iv
    * @param $data
    * @return mixed
    */
   public function decryptData( $encryptedData, $iv, &$data )
   {
       if (strlen($this->sessionKey) != 24) {
           return self::$IllegalAesKey;
       }
       $aesKey=base64_decode($this->sessionKey);


       if (strlen($iv) != 24) {
           return self::$IllegalIv;
       }
       $aesIV=base64_decode($iv);

       $aesCipher=base64_decode($encryptedData);

       $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);


       $dataObj=json_decode( $result );
       if( $dataObj  == NULL )
       {
           return self::$IllegalBuffer;
       }
       if( $dataObj->watermark->appid != $this->appid )
       {
           return self::$IllegalBuffer;
       }
       $data = $result;
       return self::$OK;
   }
}


小程序前端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//app.js
App({
  onLaunch: function () {
    // 展示本地存储能力
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)
 
    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
        this.globalData.code = res.code
      }
    })
    // 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting['scope.userInfo']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
                console.log(res)
              // 可以将 res 发送给后台解码出 unionId
              this.globalData.userInfo = res.userInfo
              wx.request({
                    url: 'http://ly.xuanwenkeji.com/wechat/login/login',
                    type:'post',
                    data: {
                      code: this.globalData.code,
                      encryptedData:res.encryptedData,
                      iv:res.iv,
                      rawData:res.rawData,
                      signature:res.signature
                    },
                    success:function(res){
                        console.log(res)
                    }
                });
              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
              // 所以此处加入 callback 以防止这种情况
              if (this.userInfoReadyCallback) {
                this.userInfoReadyCallback(res)
              }
            }
          })
        }
      }
    })
  },
  globalData: {
    userInfo: null,
    code:null
  }
})


相关评论(0)
您是不是忘了说点什么?

友情提示:垃圾评论一律封号...

还没有评论,快来抢沙发吧!