先上一张官方登录流程图:
官方解释:
小程序调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
开发者服务器以code换取 用户唯一标识openid 和 会话密钥session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
接下来将以ThinkPHP3.2框架结合小程序讲解以上流程的简单实现。
按照第一步:通过小程序调用wx.login() 获取 临时登录凭证code,并调用接口传给我们的服务器
小程序端:
utils->utils.js文件 定义http请求方法
const http = (url, callBack, method = 'GET', data = '') => {
wx.request({
url: url,
data: data,
method: method,
header: {
"content-Type": method == "GET" ? "application/json" : "application/x-www-form-urlencoded"
},
success: function (res) {
callBack(res.data);
},
fail: function (error) {
console.log(error);
}
})
}
module.exports = {
http: http
}
app.js文件
// 引入文件
var util = require("./utils/util.js")
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
var loginUrl = this.globalData.baseUrl + '/v1/login'
util.http(loginUrl, this.processLoginData, 'POST', { code: res.code })
}
})
第二步:开发者服务器以code换取 用户唯一标识openid 和 会话密钥session_key,并将openid和session_key存入缓存中,之后生成一个随机字符串(session3rd)返回给小程序端用于用户登录标识,并设置一定时间过期
服务端代码:
//登录接口
public function login()
{
// 接收小程序端传递的code
$code = I('post.code');
// 从数据库中获取appid和secret
$appid = M('WebConfig')->where(array('k' => 'weixin_appid'))->getField('v');
$secret = M('WebConfig')->where(array('k' => 'weixin_secret'))->getField('v');
// 调用微信服务器接口获取用户openid
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $appid . "&secret=" . $secret . "&js_code=" . $code . "&grant_type=authorization_code";
$res = curl_post($url);
if ($res) {
$res = json_decode($res, true);
$openid = $res['openid'];
$session_key = $res['session_key'];
}
//判断该用户是否已存在wx_user用户表
$map['openid'] = $openid;
$userinfo = M('WxUser')->where($map)->find();
//若不存则添加用户信息
if (!$userinfo) {
$timeStamp = time();
$info['openid'] = $openid;
$info['create_time'] = $timeStamp;
$info['update_time'] = $timeStamp;
M('WxUser')->add($info);
}
// 生成session3rd
// 此处为方便演示直接以时间戳代替,实际应用可自定义方法生成随机字符串
$session3rd = time();
$value['openid'] = $openid;
$value['session_key'] = $session_key;
// 缓存session3rd 并设置一个过期时间
S($session3rd, $value, 3600);
// 返回信息
$data['session3rd'] = $session3rd;
$data['msg'] = "登录成功";
$data['code'] = 200;
$this->response($data, 'json');
}
function curl_post($url, $param = array())
{
if (!is_array($param)) {
throw new Exception("参数必须为array");
}
//转成json格式
//$param=urldecode(json_encode($param));
//var_dump($param);
//$headerInfo=array('Content-Type:application/json');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); //设置访问的url地址
curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置超时
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //跟踪301
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //返回结果
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $param); //设置参数
//curl_setopt ($ch, CURLOPT_HTTPHEADER, $headerInfo);
$r = curl_exec($ch);
curl_close($ch);
return $r;
}
以上基本实现微信小程序的一个简单登录流程。
下面是关于微信小程序用户数据的签名验证和加解密:
官方图:
通过上面的步骤我们事先通过 wx.login 登录流程获取到了会话密钥 session_key 并将它保存到了缓存中,session_key对于后面我们进行
签名校验以及数据加解密有着重要作用。
按照官方步骤:
1.通过调用接口(如 wx.getUserInfo)获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )
2.开发者将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 即可校验数据的完整性。
第一步:通过wx.getUserInfo获取rawData、signature等参数,并将一系列参数和我们此前获取到的session3rd传到我们的服务器中进行校验。
// 获取用户信息
wx.getSetting({
withCredentials: true,
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
var that = this
var userInfoUrl = that.globalData.baseUrl + "/v1/login"
// 可以将 res 发送给后台解码出 unionId
that.globalData.userInfo = res.userInfo
var data = {
'rawData': res.rawData,
'signature': res.signature,
'iv': res.iv,
'encryptedData': res.encryptedData,
'session3rd': wx.getStorageSync('session3rd')
}
util.http(userInfoUrl, that.processuserInfoData, 'POST', data)
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (that.userInfoReadyCallback) {
that.userInfoReadyCallback(res)
}
}
})
}
}
})
processuserInfoData: function (data) {
console.log(data)
},
第二步,服务器接收到参数rawData、signature,并通过session3rd取出之前我们保存的session_key与rawData进行sha1加密,即:signature = sha1( rawData + session_key )
//更新用户信息
public function userInfoUpdate()
{
//获取数据
$iv = I('post.iv');
$code = I('post.code');
$signature = I('post.signature');
$encryptedData = I('post.encryptedData');
$session3rd = I('post.session3rd');
$rawData = $_POST['rawData']; // 这里不使用I方法获参,是因为I方法会过滤参数,导致rawData获取不准确
//判断session3rd是否过期
if (!S($session3rd)) {
$result['msg'] = 'session3rd不存在';
$result['code'] = 401;
$this->response($result, 'json');
}
//数据签名校验
$session3rd = S($session3rd);
$signature2 = sha1($rawData . $session3rd['session_key']);
if ($signature2 !== $signature) {
$result['msg'] = '微信数据签名校验错误';
$result['code'] = 401;
$this->response($result, 'json');
}
//数据解密
Vendor("Wx.wxBizDataCrypt");
$appid = "wx7667bca35c26f4e8";
$pc = new \WXBizDataCrypt($appid, $session3rd['session_key']);
$errCode = $pc->decryptData($encryptedData, $iv, $data);
if ($errCode == 0) {
$arr = json_decode($data, true);
$openid = $arr['openId'];
$info['unionid'] = $arr['unionId'];
$info['nickname'] = $arr['nickName'];
$info['city'] = $arr['city'];
$info['province'] = $arr['province'];
$info['country'] = $arr['country'];
$info['avatarUrl'] = $arr['avatarUrl'];
$info['sex'] = $arr['gender'];
//用户信息更新数据库
$res = M('WxUser')->where("openid='$openid'")->save($info);
if ($res) {
$result['code'] = 201;
$result['msg'] = '用户信息更新成功';
} else {
$result['code'] = 500;
$result['msg'] = '用户信息更新失败';
}
$this->response($result, 'json');
} else {
$result['msg'] = '解密错误';
$result['code'] = 401;
$this->response($result, 'json');
}
}
上面的解密类(wxBizDataCrypt)可到官方文档下载:
https://developers.weixin.qq.com/miniprogram/dev/api/signature.html#wxchecksessionobject