微信小程序 获取手机号 JavaScript解密示例代码详解


Posted in Javascript onMay 14, 2020

当我们在开发微信小程序中,有一个常用的功能,就是获取用户的手机号,然后一键登入小程序,那么手机号如何获取呢?请认真看完本文,保证可以获取到用户的手机号。

刚开始开发微信小程序的时候,想着实现手机验证码登入,后来查阅资料得知,发给用户的短信是要自己付费的。后来想想,微信获取用户的手机号一样可以保证手机号码的真实性,因为手机号既然可以绑定微信,那么肯定是被严格核验过的,然后就开始了获取手机号之旅,网上教程有很多,但不知什么原因,都是会少一些内容,有的只有前端代码,没有后端;有的后端代码是PHP,不是我们想要的 Java 或者JavaScript。我抱着开源的思想,给大家分享我获取手机号的办法,希望能帮到大家。

首先我们可以去看一看官方文档,获取手机号大致分为以下四步:

  • 第1步:使用wx.login接口获取code(临时数据)
  • 第2步:使用第一步的code,获取session_key和openid(确认用户唯一的数据)
  • 第3步:使用getPhoneNumber接口,获取iv和encryptedData(获取加密的数据)
  • 第4步:解密返回数据,获取手机号码(解密后的数据)

微信小程序 获取手机号 JavaScript解密示例代码详解

下面详细讲解:

第一步:使用wx.login接口获取code(临时数据)

官方文档是这么写的:

获取微信用户绑定的手机号,需先调用wx.login接口。
因为需要用户主动触发才能发起获取手机号接口,所以该功能不由 API 来调用,需用 button 组件的点击来触发。

注意:目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。

我们可以提炼出下面几条关键信息:

  • 只能由非个人的小程序才能获取用户手机号。
  • 获取手机号必须由button按钮组件触发,而不能写在onLoad()内自动获取。
  • 需在必要的情况下使用。

第一步获取code的代码和运行截图和第二步一起给,因为这两步必须写在一个方法内,不能单独两个方法,然后在onLoad()调用,因为小程序执行onLoad()内的方法,并不是按照代码先后顺序的(经验之谈)

第二步:使用第一步的code,获取session_key和openid(确认用户唯一的数据)

sessionkey和openid是用户的身份证明,一位用户在使用某一个小程序的时候,sessionkey是唯一的。当然一位用户在使用不同的小程序的时候,sessionkey是不一样的。

官网文档是这样写的:

需要将 button 组件 open-type 的值设置为 getPhoneNumber,当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。

我们需要拿来第一步获取到的code,来向服务器换取sessionkey和openid。

微信小程序 获取手机号 JavaScript解密示例代码详解

具体代码如下:

getLogin: function () {
 var that = this;
 wx.login({
 success: function (res) {
  console.log(res);
  that.setData({
  code: res.code,
  })
  wx.request({
  url: 'https://api.weixin.qq.com/sns/jscode2session?appid=wx846bd21xxxxxxxxx&secret=45135d68ebe49de6fe313xxxxxxxxxxx&js_code=' + that.data.code + '&grant_type=authorization_code',
  method: 'POST',
  header: {
   'content-type': 'application/json'
  },
  success: function (res) {
   console.log(res);
   that.setData({
   sessionkey: res.data.session_key,
   openid: res.data.openid,
   })
  }
  })
 }
 })
},

我们只需要在onLoad()这个生命周期函数内调用这个方法就可以了。

该方法首先调用wx.login()接口,获取到code,保存在页面变量code中,也就是第一步的操作代码。

接着调用wx.request()接口向服务器请求换取sessionkey和openid,再copy本代码的时候,你要替换掉appid和secret,这些可以在微信公众平台获取。

正常情况下,你就可以获取到sessionkey和openid了,当然如果你是个人认证的小程序,那恐怕就报错了。如果还有其他错误,欢迎在文章下方留言。

但是这只是在测试的时候可以获取,在实际运维的时候不能这样写,我们看微信官方文档的说明:

在微信开发者工具中,可以临时开启 开发环境不校验请求域名、TLS版本及HTTPS证书 选项,跳过服务器域名的校验。此时,在微信开发者工具中及手机开启调试模式时,不会进行服务器域名的校验。

在服务器域名配置成功后,建议开发者关闭此选项进行开发,并在各平台下进行测试,以确认服务器域名配置正确。

也就是说,https://api.weixin.qq.com/sns/jscode2session这个接口,我们不能直接去调用,这个时候,我们就要自己写一个jsp文件,放在Tomcat的webapp目录下,然后微信小程序通过这个jsp文件,来向微信服务器请求sessionkey和openid。

appid和secret需要自己替换。

<%@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
<%@ page language="java" import="java.net.*,java.io.*"%>
<%!
public static String GetURLstr(String strUrl)
{
 InputStream in = null;
 OutputStream out = null;
 String strdata = "";
 try
 {
 URL url = new URL(strUrl);
 in = url.openStream();
 out = System.out;
 byte[] buffer = new byte[4096];
 int bytes_read;
 while ((bytes_read = in.read(buffer)) != -1)
 {
 String reads = new String(buffer, 0, bytes_read, "UTF-8");
 strdata = strdata + reads;
 }
 in.close();
 out.close();
 return strdata;
 }
 catch (Exception e)
 {
 System.err.println(e);
 System.err.println("Usage: java GetURL <URL> [<filename>]");
 return strdata;
 }
}
%>
<%
request.setCharacterEncoding("UTF-8"); 
String str_code = "";
str_code = request.getParameter("code");
 
String str_token = "";
str_token = str_token + "https://api.weixin.qq.com/sns/jscode2session";
str_token = str_token + "?appid=wx846bd21xxxxxxxxx&secret=45135d68ebe49de6fe313xxxxxxxxxxx";
str_token = str_token + "&js_code=" + str_code ;
str_token = str_token + "&grant_type=authorization_code";
 
String neirong_token = "";
neirong_token = GetURLstr(str_token);
out.print(neirong_token);
%>

这个jsp文件需要放在Tomcat安装目录的webapp,用来被微信小程序前台来请求数据。

同时,我们微信小程序前台代码也要稍加修改。改为向jsp文件获取,传上去一个参数code。

getLogin: function () {
 var that = this;
 wx.login({
 success: function (res) {
  console.log(res);
  that.setData({
  code: res.code,
  })
  wx.request({
  url: 'https://127.0.0.1:8080/test/getOpenId.jsp?code=' + that.data.code,
  method: 'POST',
  header: {
   'content-type': 'application/json'
  },
  success: function (res) {
   console.log(res);
   that.setData({
   sessionkey: res.data.session_key,
   openid: res.data.openid,
   })
  }
  })
 }
 })
},

效果同下图所示:

微信小程序 获取手机号 JavaScript解密示例代码详解

第三步:使用getPhoneNumber接口,获取iv和encryptedData(获取加密的数据)

我们还是先来看官网文档怎么写的:

需要将 button 组件 open-type 的值设置为 getPhoneNumber,当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。

然后就是官网文档的demo:

//WXML
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
 
//JS
Page({
 getPhoneNumber (e) {
 console.log(e.detail.errMsg)
 console.log(e.detail.iv)
 console.log(e.detail.encryptedData)
 }
})

我们可以从中看出:获取手机号必须由button按钮组件触发,而不能写在onLoad()内自动获取。

也就是说,这一步不需要我们进行什么操作,只要在WXML定义一个按钮,加上open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"属性,然后在JS文件中写一个getPhoneNumber方法,该方法有一个参数e,我们可以从这个e中获取iv和encryptedData,这个encryptedData就是加密的数据,其中包括我们需要的电话号码。

那么,接下来就需要我们解密了。

第四步:解密返回数据,获取手机号码(解密后的数据)

我们还是先来看官方文档:

微信会对这些开放数据做签名和加密处理。开发者后台拿到开放数据后可以对数据进行校验签名和解密,来保证数据不被篡改。

接口如果涉及敏感数据(如wx.getUserInfo当中的 openId 和 unionId),接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据(encryptedData) 进行对称解密。 解密算法如下:

对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
对称解密的目标密文为 Base64_Decode(encryptedData)。
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
微信官方提供了多种编程语言的示例代码。每种语言类型的接口名字均一致。调用方式可以参照示例。

我们可以看出什么内容?关键的信息如下:

  • 我们获取到了sessionkey和openid,要把sessionkey和openid用来解密第三步的加密数据。
  • 我们需要用到某个高深的算法。
  • 官方提供的解密算法没有Java和JavaScript版。

我使用了JavaScript版,改解密数据的模板结构如下,我会在下面把所有的代码提供给大家。

微信小程序 获取手机号 JavaScript解密示例代码详解

这个解密算法,会把第二步获取的sessionkey和openid,第三步获取的 iv和encryptedData,解密成真正的手机号码。

我们先来看获取手机号的页面的代码:

var WXBizDataCrypt = require('../../utils/RdWXBizDataCrypt.js');
var AppId = 'wx846bd21xxxxxxxxx'
var AppSecret = '45135d68ebe49de6fe313xxxxxxxxxxx'
getPhoneNumber(e) {
 var that = this;
 console.log(e.detail.errMsg)
 console.log(e.detail.iv)
 console.log(e.detail.encryptedData)
 var pc = new WXBizDataCrypt(AppId, this.data.sessionkey)
 wx.getUserInfo({
 success: function (res) {
  var data = pc.decryptData(e.detail.encryptedData, e.detail.iv)
  console.log('解密后 data: ', data)
  console.log('手机号码: ', data.phoneNumber)
  that.setData({
  tel: data.phoneNumber,
  })
 }
 })
},

appid和secret需要自己替换。

我们先来看运行效果:

微信小程序 获取手机号 JavaScript解密示例代码详解

点击允许之后,开发工具的调试区域会打印如下信息:

微信小程序 获取手机号 JavaScript解密示例代码详解

这样就成功获取到了手机号码。

接下来是该JavaScript解密算法的部分代码,因为代码太长了,放文章里面不太合适,我会单独上传到CSDN下载模块,拿来即用即可,大家也可以在下面评论区找我要文件,笔者每天都登CSDN,谢谢大家的理解和配合。

SHA1.js

(function(){
 
var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto;
 
// Shortcuts
var util = C.util,
 charenc = C.charenc,
 UTF8 = charenc.UTF8,
 Binary = charenc.Binary;
 
// Public API
var SHA1 = C.SHA1 = function (message, options) {
	var digestbytes = util.wordsToBytes(SHA1._sha1(message));
	return options && options.asBytes ? digestbytes :
	  options && options.asString ? Binary.bytesToString(digestbytes) :
	  util.bytesToHex(digestbytes);
};
 
// The core
SHA1._sha1 = function (message) {
 
	// Convert to byte array
	if (message.constructor == String) message = UTF8.stringToBytes(message);
	/* else, assume byte array already */
 
	var m = util.bytesToWords(message),
	 l = message.length * 8,
	 w = [],
	 H0 = 1732584193,
	 H1 = -271733879,
	 H2 = -1732584194,
	 H3 = 271733878,
	 H4 = -1009589776;
 
	// Padding
	m[l >> 5] |= 0x80 << (24 - l % 32);
	m[((l + 64 >>> 9) << 4) + 15] = l;
 
	for (var i = 0; i < m.length; i += 16) {
 
		var a = H0,
		 b = H1,
		 c = H2,
		 d = H3,
		 e = H4;
 
		for (var j = 0; j < 80; j++) {
 
			if (j < 16) w[j] = m[i + j];
			else {
				var n = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16];
				w[j] = (n << 1) | (n >>> 31);
			}
 
			var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + (
			   j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 :
			   j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 :
			   j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 :
			     (H1 ^ H2 ^ H3) - 899497514);
 
			H4 = H3;
			H3 = H2;
			H2 = (H1 << 30) | (H1 >>> 2);
			H1 = H0;
			H0 = t;
 
		}
 
		H0 += a;
		H1 += b;
		H2 += c;
		H3 += d;
		H4 += e;
 
	}
 
	return [H0, H1, H2, H3, H4];
 
};
 
// Package private blocksize
SHA1._blocksize = 16;
 
SHA1._digestsize = 20;
 
})();

Crypto.js

if (typeof Crypto == "undefined" || ! Crypto.util)
{
(function(){
 
var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
// Global Crypto object
// with browser window or with node module
var Crypto = (typeof window === 'undefined') ? exports.Crypto = {} : window.Crypto = {}; 
 
// Crypto utilities
var util = Crypto.util = {
 
	// Bit-wise rotate left
	rotl: function (n, b) {
		return (n << b) | (n >>> (32 - b));
	},
 
	// Bit-wise rotate right
	rotr: function (n, b) {
		return (n << (32 - b)) | (n >>> b);
	},
 
	// Swap big-endian to little-endian and vice versa
	endian: function (n) {
 
		// If number given, swap endian
		if (n.constructor == Number) {
			return util.rotl(n, 8) & 0x00FF00FF |
			  util.rotl(n, 24) & 0xFF00FF00;
		}
 
		// Else, assume array and swap all items
		for (var i = 0; i < n.length; i++)
			n[i] = util.endian(n[i]);
		return n;
 
	},
 
	// Generate an array of any length of random bytes
	randomBytes: function (n) {
		for (var bytes = []; n > 0; n--)
			bytes.push(Math.floor(Math.random() * 256));
		return bytes;
	},
 
	// Convert a byte array to big-endian 32-bit words
	bytesToWords: function (bytes) {
		for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
			words[b >>> 5] |= (bytes[i] & 0xFF) << (24 - b % 32);
		return words;
	},
 
	// Convert big-endian 32-bit words to a byte array
	wordsToBytes: function (words) {
		for (var bytes = [], b = 0; b < words.length * 32; b += 8)
			bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
		return bytes;
	},
 
	// Convert a byte array to a hex string
	bytesToHex: function (bytes) {
		for (var hex = [], i = 0; i < bytes.length; i++) {
			hex.push((bytes[i] >>> 4).toString(16));
			hex.push((bytes[i] & 0xF).toString(16));
		}
		return hex.join("");
	},
 
	// Convert a hex string to a byte array
	hexToBytes: function (hex) {
		for (var bytes = [], c = 0; c < hex.length; c += 2)
			bytes.push(parseInt(hex.substr(c, 2), 16));
		return bytes;
	},
 
	// Convert a byte array to a base-64 string
	bytesToBase64: function (bytes) {
 
		// Use browser-native function if it exists
		if (typeof btoa == "function") return btoa(Binary.bytesToString(bytes));
 
		for(var base64 = [], i = 0; i < bytes.length; i += 3) {
			var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
			for (var j = 0; j < 4; j++) {
				if (i * 8 + j * 6 <= bytes.length * 8)
					base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
				else base64.push("=");
			}
		}
 
		return base64.join("");
 
	},
 
	// Convert a base-64 string to a byte array
	base64ToBytes: function (base64) {
 
		// Use browser-native function if it exists
		if (typeof atob == "function") return Binary.stringToBytes(atob(base64));
 
		// Remove non-base-64 characters
		base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
 
		for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) {
			if (imod4 == 0) continue;
			bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2)) |
			   (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
		}
 
		return bytes;
 
	}
 
};
 
// Crypto character encodings
var charenc = Crypto.charenc = {};
 
// UTF-8 encoding
var UTF8 = charenc.UTF8 = {
 
	// Convert a string to a byte array
	stringToBytes: function (str) {
		return Binary.stringToBytes(unescape(encodeURIComponent(str)));
	},
 
	// Convert a byte array to a string
	bytesToString: function (bytes) {
		return decodeURIComponent(escape(Binary.bytesToString(bytes)));
	}
 
};
 
// Binary encoding
var Binary = charenc.Binary = {
 
	// Convert a string to a byte array
	stringToBytes: function (str) {
		for (var bytes = [], i = 0; i < str.length; i++)
			bytes.push(str.charCodeAt(i) & 0xFF);
		return bytes;
	},
 
	// Convert a byte array to a string
	bytesToString: function (bytes) {
		for (var str = [], i = 0; i < bytes.length; i++)
			str.push(String.fromCharCode(bytes[i]));
		return str.join("");
	}
 
};
 
})();
}

CryptoMath.js

(function(){
 
var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto;
 
// Shortcut
var util = C.util;
 
// Convert n to unsigned 32-bit integer
util.u32 = function (n) {
	return n >>> 0;
};
 
// Unsigned 32-bit addition
util.add = function () {
	var result = this.u32(arguments[0]);
	for (var i = 1; i < arguments.length; i++)
		result = this.u32(result + this.u32(arguments[i]));
	return result;
};
 
// Unsigned 32-bit multiplication
util.mult = function (m, n) {
	return this.add((n & 0xFFFF0000) * m,
			(n & 0x0000FFFF) * m);
};
 
// Unsigned 32-bit greater than (>) comparison
util.gt = function (m, n) {
	return this.u32(m) > this.u32(n);
};
 
// Unsigned 32-bit less than (<) comparison
util.lt = function (m, n) {
	return this.u32(m) < this.u32(n);
};
 
})();

总结

到此这篇关于微信小程序 获取手机号 JavaScript解密示例代码详解的文章就介绍到这了,更多相关微信小程序 手机号 JavaScript解密内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js实现的网页颜色代码表全集
Jul 17 Javascript
CSS+Table图文混排中实现文本自适应图片宽度(超简单+跨所有浏览器)
Feb 14 Javascript
javascript getElementsByName()的用法说明
Jul 31 Javascript
别了 JavaScript中的isXX系列
Aug 01 Javascript
使用jQuery Mobile框架开发移动端Web App的入门教程
May 17 Javascript
jQuery 获取跨域XML(RSS)数据的相关总结分析
May 18 Javascript
jQuery Ajax 全局调用封装实例代码详解
Jun 02 Javascript
Node.js 实现简单小说爬虫实例
Nov 18 Javascript
浅谈AngularJS中$http服务的简单用法
May 15 Javascript
javascript中UMD规范的代码推演
Aug 29 Javascript
Vue+Koa2+mongoose写一个像素绘板的实现方法
Sep 10 Javascript
Element-UI+Vue模式使用总结
Jan 02 Javascript
JavaScript, select标签元素左右移动功能实现
May 14 #Javascript
vue实现商品列表的添加删除实例讲解
May 14 #Javascript
Vue 自适应高度表格的实现方法
May 13 #Javascript
ES6函数实现排它两种写法解析
May 13 #Javascript
详解vue-router的Import异步加载模块问题的解决方案
May 13 #Javascript
JavaScript 实现自己的安卓手机自动化工具脚本(推荐)
May 13 #Javascript
原生JS实现汇率转换功能代码实例
May 13 #Javascript
You might like
解析PHP实现多进程并行执行脚本
2013/06/18 PHP
浅析Yii中使用RBAC的完全指南(用户角色权限控制)
2013/06/20 PHP
PHP中批量生成静态html(命令行下运行PHP)
2014/04/19 PHP
php实现的短网址算法分享
2014/06/20 PHP
取得单条网站评论以数组形式进行输出
2014/07/28 PHP
php函数与传递参数实例分析
2014/11/15 PHP
php动态生成版权所有信息的方法
2015/03/24 PHP
微信支付开发发货通知实例
2016/07/12 PHP
网页的标准,IMG不支持onload标签怎么办
2006/06/29 Javascript
Javascript remove 自定义数组删除方法
2009/10/20 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
2013/05/06 Javascript
客户端js判断文件类型和文件大小即限制上传大小
2013/11/20 Javascript
jQuery选择器全面总结
2014/01/06 Javascript
jQuery插件multiScroll实现全屏鼠标滚动切换页面特效
2015/04/12 Javascript
深入浅析JavaScript中的Function类型
2016/07/09 Javascript
Bootstrap3 datetimepicker控件使用实例
2016/12/13 Javascript
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
React中的refs的使用教程
2018/02/13 Javascript
区别JavaScript函数声明与变量声明
2018/09/12 Javascript
three.js利用gpu选取物体并计算交点位置的方法示例
2019/11/25 Javascript
JS中数组实现代码(倒序遍历数组,数组连接字符串)
2019/12/29 Javascript
JS实现简易贪吃蛇游戏
2020/08/24 Javascript
[01:06:25]Secret vs Liquid 2018国际邀请赛淘汰赛BO3 第一场 8.25
2018/08/29 DOTA
浅谈python中列表、字符串、字典的常用操作
2017/09/19 Python
浅谈用VSCode写python的正确姿势
2017/12/16 Python
Python使用functools实现注解同步方法
2018/02/06 Python
Python封装原理与实现方法详解
2018/08/28 Python
Python OOP类中的几种函数或方法总结
2019/02/22 Python
完美解决python3.7 pip升级 拒绝访问问题
2019/07/12 Python
python超时重新请求解决方案
2019/10/21 Python
Tensorflow实现在训练好的模型上进行测试
2020/01/20 Python
Keras使用tensorboard显示训练过程的实例
2020/02/15 Python
如何将anaconda安装配置的mmdetection环境离线拷贝到另一台电脑
2020/10/15 Python
写自招自荐信的绝招!
2019/04/19 职场文书
女性励志书籍推荐
2019/08/19 职场文书
pytorch实现线性回归以及多元回归
2021/04/11 Python