微信小程序获取手机号:Java/JavaScript解密全流程解析

作者:服务小站

时间:2026-10-23

浏览:578

本文详解微信小程序获取用户手机号的完整流程,涵盖从wx.login获取code、后端换取session_key,到前端获取encryptedData及iv,最后通过AES-128-CBC算法解密获取真实手机号。提供Java与JavaScript后端解密代码示例及注意事项。

文章目录

在开发微信小程序时,获取用户手机号并实现一键登录是常见需求。本文将详细阐述如何正确获取用户手机号,请仔细阅读以确保流程无误。

获取手机号背景与方案概述

初期开发时曾考虑短信验证码登录,但发现短信发送需额外付费。考虑到微信绑定的手机号经过严格核验,其真实性有保障,因此转向研究获取手机号功能。网上教程众多,但往往缺失前端或后端代码,或仅提供PHP示例。本文旨在分享完整的Java与JavaScript实现方案,供开发者参考。

获取手机号四步流程概览

根据官方文档,获取手机号流程主要包含四个步骤:

  • 第一步:调用wx.login接口获取code(临时凭证)。
  • 第二步:利用第一步获取的code,换取session_key和openid(用户唯一标识)。
  • 第三步:调用getPhoneNumber接口,获取iv和encryptedData(加密数据)。
  • 第四步:对返回数据进行解密,从而获取手机号码。

获取手机号背景与方案概述

以下对各步骤进行详细解析:

第一步:获取临时凭证code

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

官方文档指出:

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

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

从上述说明中可提炼出以下关键点:

  • 仅非个人认证的小程序具备获取用户手机号的权限。
  • 获取手机号必须由button按钮组件触发,严禁在onLoad()中自动获取。
  • 必须在必要场景下使用此功能。

第一步获取code的代码及运行截图将在第二步中一并展示。这两步必须封装在同一个方法内,不能拆分为两个独立方法并在onLoad()中调用,因为小程序执行onLoad()内的方法并不保证代码执行的先后顺序(基于实践经验)。

第二步:后端换取session_key与openid

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

session_key和openid是用户的身份标识。在同一小程序内,一位用户拥有唯一的session_key;若用户切换不同小程序,其session_key则不相同。

官网文档说明如下:

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

我们需要利用第一步获取的code,向后端服务器请求以换取session_key和openid。

获取手机号背景与方案概述

具体实现代码如下:


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()接口向服务器请求session_key和openid。复制代码时,请替换其中的appid和secret,这些值可在微信公众平台获取。

正常情况下,即可成功获取session_key和openid。若为个人认证的小程序,预计会报错。如遇其他错误,欢迎在文章下方留言交流。

上述方式仅适用于测试环境。在实际生产运维中,不应如此直接调用,需参考微信官方文档说明:

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

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

这意味着,我们不能直接在小程序前端调用https://api.weixin.qq.com/sns/jscode2session接口。因此,需自行编写一个jsp文件,部署在Tomcat的webapp目录下,由微信小程序通过该jsp文件向微信服务器请求session_key和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,
   })
  }
  })
 }
 })
},

效果如下图所示:

获取手机号背景与方案概述

第三步:前端获取加密数据iv与encryptedData

第三步:使用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,从中可提取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由数据接口返回。 微信官方提供了多种编程语言的示例代码。每种语言类型的接口名字均一致。调用方式可以参照示例。

从中可提取关键信息:

  • 利用第二步获取的session_key和openid(主要是session_key)来解密第三步获取的加密数据。
  • 需使用特定的解密算法。
  • 官方提供的解密算法示例中未包含Java和JavaScript版本。

本文采用JavaScript版本,解密数据结构如下,后续将提供完整代码。

获取手机号背景与方案概述

该解密算法将第二步获取的session_key和第三步获取的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解密算法的部分代码。因代码较长,不便直接嵌入文章,已单独上传至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解密示例代码的完整流程。更多相关内容请搜索本站历史文章或继续浏览后续文章,希望大家多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章