微信小程序 获取手机号 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 相关文章推荐
向fckeditor编辑器插入指定代码的方法
May 25 Javascript
JavaScript delete 属性的使用
Oct 08 Javascript
javascript 设计模式之单体模式 面向对象学习基础
Apr 18 Javascript
Jquery提交表单 Form.js官方插件介绍
Mar 01 Javascript
教你如何使用node.js制作代理服务器
Nov 26 Javascript
JS判断字符串包含的方法
May 05 Javascript
JS模拟实现方法重载示例
Aug 03 Javascript
浅谈JS封闭函数、闭包、内置对象
Jul 18 Javascript
js中Object.defineProperty()方法的不详解
Jul 09 Javascript
vue移动端项目缓存问题实践记录
Oct 29 Javascript
Node 代理访问的实现
Sep 19 Javascript
浅谈 JavaScript 沙箱Sandbox
Nov 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使用curl存储cookie的示例
2014/03/31 PHP
PHP判断手机是IOS还是Android
2015/12/09 PHP
作为程序员必知的16个最佳PHP库
2015/12/09 PHP
php实现统计二进制中1的个数算法示例
2018/01/23 PHP
javascript:以前写的xmlhttp池,代码
2008/05/18 Javascript
Javascript Tab 导航插件 (23个)
2009/06/11 Javascript
javascript 函数速查表
2010/02/07 Javascript
jquery实现微博文字输入框 输入时显示输入字数 效果实现
2013/07/12 Javascript
js触发select onchange事件的小技巧
2014/08/05 Javascript
node.js中的buffer.slice方法使用说明
2014/12/10 Javascript
原生JS版和jquery版实现checkbox的全选/全不选/点选/行内点选(Mr.Think)
2016/10/29 Javascript
关于angular js_$watch监控属性和对象详解
2017/04/24 Javascript
解决vue项目报错webpackJsonp is not defined问题
2018/03/14 Javascript
解决vue-cli项目webpack打包后iconfont文件路径的问题
2018/09/01 Javascript
微信小程序实现动态列表项的顺序加载动画
2019/07/25 Javascript
微信小程序按顺序同步执行的两种方式
2019/12/20 Javascript
js函数柯里化的方法和作用实例分析
2020/04/11 Javascript
Python使用正则匹配实现抓图代码分享
2015/04/02 Python
Python中matplotlib中文乱码解决办法
2017/05/12 Python
用Python读取几十万行文本数据
2018/12/24 Python
对python修改xml文件的节点值方法详解
2018/12/24 Python
在Python中表示一个对象的方法
2019/06/25 Python
python 函数中的内置函数及用法详解
2019/07/02 Python
Django中使用极验Geetest滑动验证码过程解析
2019/07/31 Python
处理Selenium3+python3定位鼠标悬停才显示的元素
2019/07/31 Python
python实现的分析并统计nginx日志数据功能示例
2019/12/21 Python
使用python批量转换文件编码为UTF-8的实现
2020/04/03 Python
西班牙语在线票务市场:SuperBoletería
2019/06/10 全球购物
全球才华横溢工匠的家居装饰、珠宝和礼物:NOVICA
2021/01/22 全球购物
化妆师职业生涯规划书
2014/02/16 职场文书
本科生就业推荐信
2014/05/19 职场文书
2015年教师学期工作总结
2015/04/30 职场文书
宇宙与人观后感
2015/06/05 职场文书
亮剑观后感
2015/06/05 职场文书
Flask response响应的具体使用
2021/07/15 Python
详解Flutter和Dart取消Future的三种方法
2022/04/07 Java/Android