微信小程序 獲取手機(jī)號(hào) JavaScript解密示例代碼詳解
當(dāng)我們?cè)陂_(kāi)發(fā)微信小程序中,有一個(gè)常用的功能,就是獲取用戶的手機(jī)號(hào),然后一鍵登入小程序,那么手機(jī)號(hào)如何獲取呢?請(qǐng)認(rèn)真看完本文,保證可以獲取到用戶的手機(jī)號(hào)。
剛開(kāi)始開(kāi)發(fā)微信小程序的時(shí)候,想著實(shí)現(xiàn)手機(jī)驗(yàn)證碼登入,后來(lái)查閱資料得知,發(fā)給用戶的短信是要自己付費(fèi)的。后來(lái)想想,微信獲取用戶的手機(jī)號(hào)一樣可以保證手機(jī)號(hào)碼的真實(shí)性,因?yàn)槭謾C(jī)號(hào)既然可以綁定微信,那么肯定是被嚴(yán)格核驗(yàn)過(guò)的,然后就開(kāi)始了獲取手機(jī)號(hào)之旅,網(wǎng)上教程有很多,但不知什么原因,都是會(huì)少一些內(nèi)容,有的只有前端代碼,沒(méi)有后端;有的后端代碼是PHP,不是我們想要的 Java 或者JavaScript。我抱著開(kāi)源的思想,給大家分享我獲取手機(jī)號(hào)的辦法,希望能幫到大家。
首先我們可以去看一看官方文檔,獲取手機(jī)號(hào)大致分為以下四步:
- 第1步:使用wx.login接口獲取code(臨時(shí)數(shù)據(jù))
- 第2步:使用第一步的code,獲取session_key和openid(確認(rèn)用戶唯一的數(shù)據(jù))
- 第3步:使用getPhoneNumber接口,獲取iv和encryptedData(獲取加密的數(shù)據(jù))
- 第4步:解密返回?cái)?shù)據(jù),獲取手機(jī)號(hào)碼(解密后的數(shù)據(jù))
下面詳細(xì)講解:
第一步:使用wx.login接口獲取code(臨時(shí)數(shù)據(jù))
官方文檔是這么寫的:
獲取微信用戶綁定的手機(jī)號(hào),需先調(diào)用wx.login接口。
因?yàn)樾枰脩糁鲃?dòng)觸發(fā)才能發(fā)起獲取手機(jī)號(hào)接口,所以該功能不由 API 來(lái)調(diào)用,需用 button 組件的點(diǎn)擊來(lái)觸發(fā)。注意:目前該接口針對(duì)非個(gè)人開(kāi)發(fā)者,且完成了認(rèn)證的小程序開(kāi)放(不包含海外主體)。需謹(jǐn)慎使用,若用戶舉報(bào)較多或被發(fā)現(xiàn)在不必要場(chǎng)景下使用,微信有權(quán)永久回收該小程序的該接口權(quán)限。
我們可以提煉出下面幾條關(guān)鍵信息:
- 只能由非個(gè)人的小程序才能獲取用戶手機(jī)號(hào)。
- 獲取手機(jī)號(hào)必須由button按鈕組件觸發(fā),而不能寫在onLoad()內(nèi)自動(dòng)獲取。
- 需在必要的情況下使用。
第一步獲取code的代碼和運(yùn)行截圖和第二步一起給,因?yàn)?strong>這兩步必須寫在一個(gè)方法內(nèi),不能單獨(dú)兩個(gè)方法,然后在onLoad()調(diào)用,因?yàn)樾〕绦驁?zhí)行onLoad()內(nèi)的方法,并不是按照代碼先后順序的(經(jīng)驗(yàn)之談)
第二步:使用第一步的code,獲取session_key和openid(確認(rèn)用戶唯一的數(shù)據(jù))
sessionkey和openid是用戶的身份證明,一位用戶在使用某一個(gè)小程序的時(shí)候,sessionkey是唯一的。當(dāng)然一位用戶在使用不同的小程序的時(shí)候,sessionkey是不一樣的。
官網(wǎng)文檔是這樣寫的:
需要將 button 組件 open-type 的值設(shè)置為 getPhoneNumber,當(dāng)用戶點(diǎn)擊并同意之后,可以通過(guò) bindgetphonenumber 事件回調(diào)獲取到微信服務(wù)器返回的加密數(shù)據(jù), 然后在第三方服務(wù)端結(jié)合 session_key 以及 app_id 進(jìn)行解密獲取手機(jī)號(hào)。
我們需要拿來(lái)第一步獲取到的code,來(lái)向服務(wù)器換取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()這個(gè)生命周期函數(shù)內(nèi)調(diào)用這個(gè)方法就可以了。
該方法首先調(diào)用wx.login()接口,獲取到code,保存在頁(yè)面變量code中,也就是第一步的操作代碼。
接著調(diào)用wx.request()接口向服務(wù)器請(qǐng)求換取sessionkey和openid,再copy本代碼的時(shí)候,你要替換掉appid和secret,這些可以在微信公眾平臺(tái)獲取。
正常情況下,你就可以獲取到sessionkey和openid了,當(dāng)然如果你是個(gè)人認(rèn)證的小程序,那恐怕就報(bào)錯(cuò)了。如果還有其他錯(cuò)誤,歡迎在文章下方留言。
但是這只是在測(cè)試的時(shí)候可以獲取,在實(shí)際運(yùn)維的時(shí)候不能這樣寫,我們看微信官方文檔的說(shuō)明:
在微信開(kāi)發(fā)者工具中,可以臨時(shí)開(kāi)啟 開(kāi)發(fā)環(huán)境不校驗(yàn)請(qǐng)求域名、TLS版本及HTTPS證書 選項(xiàng),跳過(guò)服務(wù)器域名的校驗(yàn)。此時(shí),在微信開(kāi)發(fā)者工具中及手機(jī)開(kāi)啟調(diào)試模式時(shí),不會(huì)進(jìn)行服務(wù)器域名的校驗(yàn)。
在服務(wù)器域名配置成功后,建議開(kāi)發(fā)者關(guān)閉此選項(xiàng)進(jìn)行開(kāi)發(fā),并在各平臺(tái)下進(jìn)行測(cè)試,以確認(rèn)服務(wù)器域名配置正確。
也就是說(shuō),https://api.weixin.qq.com/sns/jscode2session這個(gè)接口,我們不能直接去調(diào)用,這個(gè)時(shí)候,我們就要自己寫一個(gè)jsp文件,放在Tomcat的webapp目錄下,然后微信小程序通過(guò)這個(gè)jsp文件,來(lái)向微信服務(wù)器請(qǐng)求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); %>
這個(gè)jsp文件需要放在Tomcat安裝目錄的webapp,用來(lái)被微信小程序前臺(tái)來(lái)請(qǐng)求數(shù)據(jù)。
同時(shí),我們微信小程序前臺(tái)代碼也要稍加修改。改為向jsp文件獲取,傳上去一個(gè)參數(shù)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(獲取加密的數(shù)據(jù))
我們還是先來(lái)看官網(wǎng)文檔怎么寫的:
需要將 button 組件 open-type 的值設(shè)置為 getPhoneNumber,當(dāng)用戶點(diǎn)擊并同意之后,可以通過(guò) bindgetphonenumber 事件回調(diào)獲取到微信服務(wù)器返回的加密數(shù)據(jù), 然后在第三方服務(wù)端結(jié)合 session_key 以及 app_id 進(jìn)行解密獲取手機(jī)號(hào)。
然后就是官網(wǎng)文檔的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) } })
我們可以從中看出:獲取手機(jī)號(hào)必須由button按鈕組件觸發(fā),而不能寫在onLoad()內(nèi)自動(dòng)獲取。
也就是說(shuō),這一步不需要我們進(jìn)行什么操作,只要在WXML定義一個(gè)按鈕,加上open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"屬性,然后在JS文件中寫一個(gè)getPhoneNumber方法,該方法有一個(gè)參數(shù)e,我們可以從這個(gè)e中獲取iv和encryptedData,這個(gè)encryptedData就是加密的數(shù)據(jù),其中包括我們需要的電話號(hào)碼。
那么,接下來(lái)就需要我們解密了。
第四步:解密返回?cái)?shù)據(jù),獲取手機(jī)號(hào)碼(解密后的數(shù)據(jù))
我們還是先來(lái)看官方文檔:
微信會(huì)對(duì)這些開(kāi)放數(shù)據(jù)做簽名和加密處理。開(kāi)發(fā)者后臺(tái)拿到開(kāi)放數(shù)據(jù)后可以對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)簽名和解密,來(lái)保證數(shù)據(jù)不被篡改。
接口如果涉及敏感數(shù)據(jù)(如wx.getUserInfo當(dāng)中的 openId 和 unionId),接口的明文內(nèi)容將不包含這些敏感數(shù)據(jù)。開(kāi)發(fā)者如需要獲取敏感數(shù)據(jù),需要對(duì)接口返回的加密數(shù)據(jù)(encryptedData) 進(jìn)行對(duì)稱解密。 解密算法如下:
對(duì)稱解密使用的算法為 AES-128-CBC,數(shù)據(jù)采用PKCS#7填充。
對(duì)稱解密的目標(biāo)密文為 Base64_Decode(encryptedData)。
對(duì)稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節(jié)。
對(duì)稱解密算法初始向量 為Base64_Decode(iv),其中iv由數(shù)據(jù)接口返回。
微信官方提供了多種編程語(yǔ)言的示例代碼。每種語(yǔ)言類型的接口名字均一致。調(diào)用方式可以參照示例。
我們可以看出什么內(nèi)容?關(guān)鍵的信息如下:
- 我們獲取到了sessionkey和openid,要把sessionkey和openid用來(lái)解密第三步的加密數(shù)據(jù)。
- 我們需要用到某個(gè)高深的算法。
- 官方提供的解密算法沒(méi)有Java和JavaScript版。
我使用了JavaScript版,改解密數(shù)據(jù)的模板結(jié)構(gòu)如下,我會(huì)在下面把所有的代碼提供給大家。
這個(gè)解密算法,會(huì)把第二步獲取的sessionkey和openid,第三步獲取的 iv和encryptedData,解密成真正的手機(jī)號(hào)碼。
我們先來(lái)看獲取手機(jī)號(hào)的頁(yè)面的代碼:
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('手機(jī)號(hào)碼: ', data.phoneNumber) that.setData({ tel: data.phoneNumber, }) } }) },
appid和secret需要自己替換。
我們先來(lái)看運(yùn)行效果:
點(diǎn)擊允許之后,開(kāi)發(fā)工具的調(diào)試區(qū)域會(huì)打印如下信息:
這樣就成功獲取到了手機(jī)號(hào)碼。
接下來(lái)是該JavaScript解密算法的部分代碼,因?yàn)榇a太長(zhǎng)了,放文章里面不太合適,我會(huì)單獨(dú)上傳到CSDN下載模塊,拿來(lái)即用即可,大家也可以在下面評(píng)論區(qū)找我要文件,筆者每天都登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); }; })();
總結(jié)
到此這篇關(guān)于微信小程序 獲取手機(jī)號(hào) JavaScript解密示例代碼詳解的文章就介紹到這了,更多相關(guān)微信小程序 手機(jī)號(hào) JavaScript解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
select標(biāo)簽設(shè)置默認(rèn)選中的選項(xiàng)方法
下面小編就為大家分享一篇select標(biāo)簽設(shè)置默認(rèn)選中的選項(xiàng)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03一個(gè)檢測(cè)表單數(shù)據(jù)的JavaScript實(shí)例
這篇文章主要介紹了一個(gè)檢測(cè)表單數(shù)據(jù)的JavaScript實(shí)例,很簡(jiǎn)單,很實(shí)用,比較適合初學(xué)者2014-10-10JS中數(shù)組與對(duì)象的遍歷方法實(shí)例小結(jié)
這篇文章主要介紹了JS中數(shù)組與對(duì)象的遍歷方法,結(jié)合實(shí)例形式總結(jié)分析了JavaScript針對(duì)數(shù)組與對(duì)象遍歷操作相關(guān)函數(shù)與使用技巧,需要的朋友可以參考下2018-08-08JavaScript獲得當(dāng)前網(wǎng)頁(yè)來(lái)源頁(yè)面(即上一頁(yè))的方法
這篇文章主要介紹了JavaScript獲得當(dāng)前網(wǎng)頁(yè)來(lái)源頁(yè)面(即上一頁(yè))的方法,涉及javascript中document.referrer方法的使用技巧,需要的朋友可以參考下2015-04-04JS按鈕倒計(jì)時(shí)并跳轉(zhuǎn)到新地址的實(shí)現(xiàn)代碼
在完成某項(xiàng)操作時(shí),按鈕上有個(gè)倒計(jì)時(shí)效果,倒計(jì)時(shí)結(jié)束后,跳轉(zhuǎn)到新地址,這篇文章主要介紹了JS按鈕倒計(jì)時(shí)并跳轉(zhuǎn)到新地址,需要的朋友可以參考下2023-02-02Add a Picture to a Microsoft Word Document
Add a Picture to a Microsoft Word Document...2007-06-06JavaScript設(shè)計(jì)模式之外觀模式介紹
這篇文章主要介紹了JavaScript設(shè)計(jì)模式之外觀模式介紹,外觀模式是用于由于子系統(tǒng)或程序組成較復(fù)雜而提供的一個(gè)高層界面接口,使用客戶端更容易訪問(wèn)底層的程序或系統(tǒng)接口,需要的朋友可以參考下2014-12-12