本文原創首發CSDN,鏈接 https://blog.csdn.net/qq_41464123/article/details/105214094 ,作者博客https://blog.csdn.net/qq_41464123 ,轉載請帶上本段內容,尤其是腳本之家、碼神島等平台,謝謝配合。
目錄
第二步:使用第一步的code,獲取session_key和openid(確認用戶唯一的數據)
第三步:使用getPhoneNumber接口,獲取iv和encryptedData(獲取加密的數據)
說明:筆者重新規划了博客方向,想更詳細的講解微信小程序的所有技術內容,本文於2020年5月25日已做修改。
本文首發CSDN,純筆者原創手打,歡迎社會各界朋友來轉載我的文章,希望先來私信我,轉載后文章加上原文地址!
同時筆者也歡迎一起合作共贏,願意寫雜志,寫書,貢獻自己的一份微薄之力!
當我們在開發微信小程序中,有一個常用的功能,就是獲取用戶的手機號,然后一鍵登入小程序,那么手機號如何獲取呢?請認真看完本文,保證可以獲取到用戶的手機號。
如果您想系統的學習微信小程序,歡迎關注我的CSDN微信小程序專欄,我將不定期更新所學技術,謝謝!
剛開始開發微信小程序的時候,想着實現手機驗證碼登入,后來查閱資料得知,發給用戶的短信是要自己付費的。后來想想,微信獲取用戶的手機號一樣可以保證手機號碼的真實性,因為手機號既然可以綁定微信,那么肯定是被嚴格核驗過的,然后就開始了獲取手機號之旅,網上教程有很多,但不知什么原因,都是會少一些內容,有的只有前端代碼,沒有后端;有的后端代碼是PHP,不是我們想要的 Java 或者JavaScript。我抱着開源的思想,給大家分享我獲取手機號的辦法,希望能幫到大家。
首先我們可以去看一看官方文檔,獲取手機號大致分為以下四步:
- 第1步:使用wx.login接口獲取code(臨時數據)
- 第2步:使用第一步的code,獲取session_key和openid(確認用戶唯一的數據)
- 第3步:使用getPhoneNumber接口,獲取iv和encryptedData(獲取加密的數據)
- 第4步:解密返回數據,獲取手機號碼(解密后的數據)
下面詳細講解:
第一步:使用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。
具體代碼如下:
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,
})
}
})
}
})
},
效果同下圖所示:
第三步:使用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版,改解密數據的模板結構如下,我會在下面把所有的代碼提供給大家。
這個解密算法,會把第二步獲取的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解密算法的部分代碼,因為代碼太長了,放文章里面不太合適,我會單獨上傳到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);
};
})();