thumbnail
uni-app微信授权,新版4.28,多种方式校验临时凭证code来获取openid

前言

注意:
  1. 2021年4月28日24时后发布的小程序新版本,无法通过wx.getUserInfo与 open-type = getUserInfo 获取用户个人信息
  2. 新增 getUserProfile 接口,可获取用户头像、昵称、性别及地区信息,每次通过该接口获取用户个人信息均需用户确认

好家伙,3月份做的登录授权到4月就不能用了,那就只能乖乖听话改方法了。

不过新版方式可以组件调用授权方法,只要使用了uni.getUserProfile(OBJECT) 方法才会弹出授权窗口,也还是挺方便的



一、授权流程

1.流程图

附一张官方图

这是腾讯官方规定的,想要兑换唯一凭证,就必须要通过后端安全处理,假设你的uni-app中用 request 或者 uniCloud云函数 方式获取到了openid,那么发布上线后依然是不能用的,小程序会对合法域名进行严格校验

在这里插入图片描述

简要说明:

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
  2. 后端通过调用接口换取用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key

2.授权操作

首先定义个三方登录按钮吧

样式可自定义,这里就是一个普通按钮,上面注释掉的Button为旧的授权登录方式

<view class="footer">
	<view class="other_login">
		<!-- <button class="btn-phone" open-type="getUserInfo" @getuserinfo="appLoginWx"></button> -->
		<button class="btn-phone" @click="appLoginWx"></button>
	</view>
	<text class="text-white">WeChat</text>
</view>

在这里插入图片描述

下面开始分步处理


1.加载登录页判断缓存,自动登录

onLoad() {
	let u = uni.getStorageSync('userInfo')
	if (u._id) {
		uni.showLoading({
			title:"登录中...",
			mask: true
		})
		setTimeout(()=>{
			uni.hideLoading()
			uni.switchTab({
				url: '/pages/index/index'
			})
		},1000)
	}
},

2.授权同意与拒绝

let self = this
uni.getUserProfile({
	desc: '向小红帽报告一下信息',
	success: userProfile => {
		console.log('微信用户数据', userProfile)
		// 同意授权后
		// ...
	},
	fail: () => {
		uni.showToast({
			title: "您取消了授权",
			icon: "none",
			duration: 2000
		})
	}
});

在这里插入图片描述

userInfo : 用户信息对象
rawData : 不包括敏感信息的原始数据字符串,用于计算签名
signature : 使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息
encryptedData : 包括敏感数据在内的完整用户信息的加密数据
iv : 加密算法的初始向量
cloudID : 敏感数据对应的云 ID,开通云开发的小程序才会返回,可通过云调用直接获取开放数据
errMsg : 描述信息

我们暂时用到userInfo里的数据:头像,昵称,性别


3.获取服务供应商

获取当前工程环境中可用的供应商

uni.getProvider({
	service: 'oauth',
	success: wxProvider => {
		console.log('服务供应商', wxProvider)
		if (~wxProvider.provider.indexOf('weixin')) {
			// 可使用weixin供应商,开始微信登录
			// ...
			
		} else {
			uni.showToast({
				title: '请先安装微信或升级版本',
				icon: "none",
				duration: 2000
			});
		}
	}
});

在这里插入图片描述

我们这里是授权登录,所以service服务类型是oauth,provider是得到的服务供应商

oauth : 登录类
share : 分享类
payment : 支付类
push : 推送类


4.微信登录

uni.login({
	provider: 'weixin',
	success: wxCodeRes => {
		console.log('微信CODE响应', wxCodeRes)
		// 使用临时登录凭证CODE换取唯一凭证
		// ...
		
	},
	fail: () => {
		uni.showToast({
			title: "微信登录失败",
			icon: "none",
			duration: 2000
		});
	}
})

在这里插入图片描述
然后才是重头戏,我们有很多很多方式可以获取唯一凭证,下面我举两个例子,一个是利用 uniCloud 云函数,一个是利用 Nodejs Express 服务器,其实最好还是交给java处理


5.换取openid标识

uniCloud 云函数 wx_login

'use strict';  
const loginConfig = {
  AppId: '', //微信小程序AppId
  AppSecret: '' //微信小程序AppSecret
}
exports.main = async (event, context) => {  
	let data = {
		appid: loginConfig.AppId,
		secret: loginConfig.AppSecret,
		js_code: event.code,
		grant_type: 'authorization_code'
	}
	const res = await uniCloud.httpclient.request('https://api.weixin.qq.com/sns/jscode2session', {
		method: 'GET',
		data,
		dataType: 'json'
	})	
	const success = res.status === 200 && res.data && res.data.openid
	if (!success) {
		return {
			status: 500,
			msg: '从微信获取登录信息失败'
		}
	} else {
		return {
			data: res.data,
			msg: '获取OPENID&SESSIONKEY成功'
		}
	}
};

在这里插入图片描述

简单说明:

调用代码

uniCloud.callFunction({
	name: 'wx_login',
	data: { code: wxCodeRes.code },
	success(wxOpenRes) {
		console.log('微信OPEN响应', wxOpenRes)
		// 对接数据库操作账号
		// ...
		
	},
	fail(e) {
		self.loadWxModal(false)
		console.log(e)
	}
})

express服务器换取

import { fetchGet , fetchPost } from './utils/selfhttp.js'

export default (query, request) => {
  return new Promise((resolve, reject) => {
    fetchGet('https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code='+
    query.code+'&grant_type=authorization_code ', {})
      .then(res => {
        resolve(res.data)
      })
      .catch((e) => {
        reject(e)
      })
  })
}

为了测试,简单使用了下axios

import axios from 'axios'
// 响应时间
axios.defaults.timeout = 8 * 1000
// 配置cookie
// axios.defaults.withCredentials = true
// 配置请求头
// axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
// 配置接口地址
axios.defaults.baseURL = ''
// POST传参序列化(添加请求拦截器)
axios.interceptors.request.use(
  config => {
    return config
  },
  err => {
    return Promise.reject(err)
  }
)
// 返回状态判断(添加响应拦截器)
axios.interceptors.response.use(
  res => {
    return res
  },
  err => {
    return Promise.reject(err)
  }
)
// 发送请求
export function fetchPost (url, params={}) {
  return new Promise((resolve, reject) => {
    axios
      .post(url, params)
      .then(
        res => {
          resolve(res)
        },
        err => {
          reject(err)
         
        }
      )
      .catch(err => {
        reject(err)
          console.log(err);
      })
  })
}
export function fetchGet (url, params={}) {
  return new Promise((resolve, reject) => {
    axios
      .get(url, {
        params: params
      })
      .then(res => {
        resolve(res)
      })
      .catch(err => {
        reject(err)
      })
  })
}

调用代码

切记,你的后端需要时https协议并在小程序管理平台添加为合法域名,否则上线后将功亏一篑,你们可以在各自后端中发送相应的请求和参数,总之最后获取到 openidsession_key 就行了

self.$http.get('后端URL', { code: wxCodeRes.code }, {tk: '自定义后端访问TOKEN'}, {})
.then(wxOpenRes => {
	console.log('微信OPEN响应', wxOpenRes)
	self.userInfo.openid = wxOpenRes.openid
	self.userInfo.session_key = wxOpenRes.session_key
	self.userInfo.nickName = u.nickName
	self.userInfo.avatarUrl = u.avatarUrl
	self.userInfo.gender = u.gender																	
	// 查询数据库是否有该账号
	// ...
	// 有则直接登录
	// 无则添加后再登录
	
})

3.完整代码

// 控制加载弹窗
loadWxModal(e) {
	this.loadModal = e
},
appLoginWx() {
	let self = this
	uni.getUserProfile({
		desc: '向小红帽报告一下信息',
		success: userProfile => {
			console.log('微信用户数据', userProfile)
			self.loadWxModal(true)
			let u = userProfile.userInfo
			uni.getProvider({
				service: 'oauth',
				success: wxProvider => {
					console.log('服务供应商', wxProvider)
					if (~wxProvider.provider.indexOf('weixin')) {
						uni.login({
							provider: 'weixin',
							success: wxCodeRes => {
								console.log('微信CODE响应', wxCodeRes)
								self.$http.get('https://xxx/wx/login', { code: wxCodeRes.code }, {tk: 'xxx'}, {})
									.then(wxOpenRes => {
										console.log('微信OPEN响应', wxOpenRes)
										self.userInfo.openid = wxOpenRes.openid
										self.userInfo.session_key = wxOpenRes.session_key
										self.userInfo.nickName = u.nickName
										self.userInfo.avatarUrl = u.avatarUrl
										self.userInfo.gender = u.gender																	
										self.userInfo.createTime = new Date()
										self.userInfo.userPhone = ''
										// 查询数据库是否有该账号,有则登录,无则添加
										// ...
									})
							},
							fail: () => {
								self.loadWxModal(false)
								uni.showToast({
									title: "微信登录失败",
									icon: "none",
									duration: 2000
								});
							}
						})
					} else {
						self.loadWxModal(false)
						uni.showToast({
							title: '请先安装微信或升级版本',
							icon: "none",
							duration: 2000
						});
					}
				}
			});
		},
		fail: () => {
			uni.showToast({
				title: "您取消了授权",
				icon: "none",
				duration: 2000
			})
		}
	});
}

当然也可分开同步封装调用,也要记得用户登录存缓存一份


二、授权手机号

这个就简单也说下吧,但是慎重,乱用会被腾讯制裁的,毕竟直接把人家手机号都怼到手了,这个被举报了很严重的,咳咳

不过非企业是无法在小程序中进行微信认证,也就是说只能测试玩玩,上线后不能使用该功能


1.Button

<view class="margin-top padding-lr-xl flex flex-direction">
	<button class="btn-wx" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号</button>
</view>

2.解密数据

导入工具类

import WXBizDataCrypt from '@/common/WXBizDataCrypt.js'

授权获取手机号流程

  • 需要你的appid
  • 需要你当前登录的账号的session_key
  • 需要你同意授权,然后获取到2个值
  • 对以上4条数据进行解密就能得到你想要的了
// 获取wechat手机号
getPhoneNumber(e) {
	let self = this
	if (e.detail.errMsg === "getPhoneNumber:ok") {
		let c = new WXBizDataCrypt(self.appid, self.userInfo.session_key);
		let data = c.decryptData(e.detail.encryptedData, e.detail.iv);
		self.userInfo.userPhone = data .phoneNumber
		// 修改微信账号手机号
		// ...
		
	} else {
		uni.showToast({
			title: "已拒绝获取微信手机号",
			icon: "none",
			duration: 2000
		})
	}
},

如果授权同意将获取到 encryptedDataiv,然后解密后的数据就是我们需要的了

phoneNumber : 用户绑定的手机号(国外手机号会有区号)
purePhoneNumber : 没有区号的手机号
countryCode : 区号


3.WXBizDataCrypt.js

var crypto = require('crypto')

function WXBizDataCrypt(appId, sessionKey) {
  this.appId = appId
  this.sessionKey = sessionKey
}

WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
  // base64 decode
  var sessionKey = new Buffer(this.sessionKey, 'base64')
  encryptedData = new Buffer(encryptedData, 'base64')
  iv = new Buffer(iv, 'base64')

  try {
     // 解密
    var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
    // 设置自动 padding 为 true,删除填充补位
    decipher.setAutoPadding(true)
    var decoded = decipher.update(encryptedData, 'binary', 'utf8')
    decoded += decipher.final('utf8')
    
    decoded = JSON.parse(decoded)
    
  } catch (err) {
    throw new Error('Illegal Buffer')
  }
  if (decoded.watermark.appid !== this.appId) {
    throw new Error('Illegal Buffer')
  }
  return decoded
}
module.exports = WXBizDataCrypt

总结

小程序上线坑很多,大家需要好好填充

上一篇
下一篇