在使用php作为服务端语言获取微信小程序用户信息和完成登录逻辑时,我们先来分析下官网给出的登录流程图。
看图,我们把流程大概分为以下几个步骤
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 以备验证该用户和返回数据
如何获取用户信息?
数据签名校验
为了确保 开放接口 返回用户数据的安全性,微信会对明文数据进行签名。开发者可以根据业务需要对数据包进行签名校验,确保数据的完整性。
通过调用接口(如 wx.getUserInfo)获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )
开发者将 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 { if ( empty ( $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 } }) |
友情提示:垃圾评论一律封号...