刚开始开发微信小程序的时候,想着实现手机验证码登入,后来查阅资料得知,发给用户的短信是要自己付费的。后来想想,微信获取用户的手机号一样可以保证手机号码的真实性,因为手机号既然可以绑定微信,那么肯定是被严格核验过的,然后就开始了获取手机号之旅,网上教程有很多,但不知什么原因,都是会少一些内容,有的只有前端代码,没有后端;有的后端代码是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。
具体代码如下:
1. getLogin: function () { 2. var that = this; 3. wx.login({ 4. success: function (res) { 5. console.log(res); 6. that.setData({ 7. code: res.code, 8. }) 9. wx.request({ 10. url: 'https://api.weixin.qq.com/sns/jscode2session?appid=wx846bd21xxxxxxxxx&secret=45135d68ebe49de6fe313xxxxxxxxxxx&js_code=' + that.data.code + '&grant_type=authorization_code', 11. method: 'POST', 12. header: { 13. 'content-type': 'application/json' 14. }, 15. success: function (res) { 16. console.log(res); 17. that.setData({ 18. sessionkey: res.data.session_key, 19. openid: res.data.openid, 20. }) 21. } 22. }) 23. } 24. }) 25. },
我们只需要在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需要自己替换。
1. <%@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %> 2. <%@ page language="java" import="java.net.*,java.io.*"%> 3. <%! 4. public static String GetURLstr(String strUrl) 5. { 6. InputStream in = null; 7. OutputStream out = null; 8. String strdata = ""; 9. try 10. { 11. URL url = new URL(strUrl); 12. in = url.openStream(); 13. out = System.out; 14. byte[] buffer = new byte[4096]; 15. int bytes_read; 16. while ((bytes_read = in.read(buffer)) != -1) 17. { 18. String reads = new String(buffer, 0, bytes_read, "UTF-8"); 19. strdata = strdata + reads; 20. } 21. in.close(); 22. out.close(); 23. return strdata; 24. } 25. catch (Exception e) 26. { 27. System.err.println(e); 28. System.err.println("Usage: java GetURL <URL> [<filename>]"); 29. return strdata; 30. } 31. } 32. %> 33. <% 34. request.setCharacterEncoding("UTF-8"); 35. String str_code = ""; 36. str_code = request.getParameter("code"); 37. 38. String str_token = ""; 39. str_token = str_token + "https://api.weixin.qq.com/sns/jscode2session"; 40. str_token = str_token + "?appid=wx846bd21xxxxxxxxx&secret=45135d68ebe49de6fe313xxxxxxxxxxx"; 41. str_token = str_token + "&js_code=" + str_code ; 42. str_token = str_token + "&grant_type=authorization_code"; 43. 44. String neirong_token = ""; 45. neirong_token = GetURLstr(str_token); 46. out.print(neirong_token); 47. %>
这个jsp文件需要放在Tomcat安装目录的webapp,用来被微信小程序前台来请求数据。
同时,我们微信小程序前台代码也要稍加修改。改为向jsp文件获取,传上去一个参数code。
1. getLogin: function () { 2. var that = this; 3. wx.login({ 4. success: function (res) { 5. console.log(res); 6. that.setData({ 7. code: res.code, 8. }) 9. wx.request({ 10. url: 'https://127.0.0.1:8080/test/getOpenId.jsp?code=' + that.data.code, 11. method: 'POST', 12. header: { 13. 'content-type': 'application/json' 14. }, 15. success: function (res) { 16. console.log(res); 17. that.setData({ 18. sessionkey: res.data.session_key, 19. openid: res.data.openid, 20. }) 21. } 22. }) 23. } 24. }) 25. },
效果同下图所示:
第三步:使用getPhoneNumber接口,获取iv和encryptedData(获取加密的数据)
我们还是先来看官网文档怎么写的:
需要将 button 组件 open-type 的值设置为 getPhoneNumber,当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。
然后就是官网文档的demo:
1. //WXML 2. <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> 3. 4. //JS 5. Page({ 6. getPhoneNumber (e) { 7. console.log(e.detail.errMsg) 8. console.log(e.detail.iv) 9. console.log(e.detail.encryptedData) 10. } 11. })
我们可以从中看出:获取手机号必须由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,解密成真正的手机号码。
我们先来看获取手机号的页面的代码:
1. var WXBizDataCrypt = require('../../utils/RdWXBizDataCrypt.js'); 2. var AppId = 'wx846bd21xxxxxxxxx' 3. var AppSecret = '45135d68ebe49de6fe313xxxxxxxxxxx' 4. getPhoneNumber(e) { 5. var that = this; 6. console.log(e.detail.errMsg) 7. console.log(e.detail.iv) 8. console.log(e.detail.encryptedData) 9. var pc = new WXBizDataCrypt(AppId, this.data.sessionkey) 10. wx.getUserInfo({ 11. success: function (res) { 12. var data = pc.decryptData(e.detail.encryptedData, e.detail.iv) 13. console.log('解密后 data: ', data) 14. console.log('手机号码: ', data.phoneNumber) 15. that.setData({ 16. tel: data.phoneNumber, 17. }) 18. } 19. }) 20. },
appid和secret需要自己替换。
我们先来看运行效果:
点击允许之后,开发工具的调试区域会打印如下信息:
这样就成功获取到了手机号码。
接下来是该JavaScript解密算法的部分代码,因为代码太长了,放文章里面不太合适,我会单独上传到CSDN下载模块,拿来即用即可,大家也可以在下面评论区找我要文件,笔者每天都登CSDN,谢谢大家的理解和配合。
SHA1.js
1. (function(){ 2. 3. var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto; 4. 5. // Shortcuts 6. var util = C.util, 7. charenc = C.charenc, 8. UTF8 = charenc.UTF8, 9. Binary = charenc.Binary; 10. 11. // Public API 12. var SHA1 = C.SHA1 = function (message, options) { 13. var digestbytes = util.wordsToBytes(SHA1._sha1(message)); 14. return options && options.asBytes ? digestbytes : 15. options && options.asString ? Binary.bytesToString(digestbytes) : 16. util.bytesToHex(digestbytes); 17. }; 18. 19. // The core 20. SHA1._sha1 = function (message) { 21. 22. // Convert to byte array 23. if (message.constructor == String) message = UTF8.stringToBytes(message); 24. /* else, assume byte array already */ 25. 26. var m = util.bytesToWords(message), 27. l = message.length * 8, 28. w = [], 29. H0 = 1732584193, 30. H1 = -271733879, 31. H2 = -1732584194, 32. H3 = 271733878, 33. H4 = -1009589776; 34. 35. // Padding 36. m[l >> 5] |= 0x80 << (24 - l % 32); 37. m[((l + 64 >>> 9) << 4) + 15] = l; 38. 39. for (var i = 0; i < m.length; i += 16) { 40. 41. var a = H0, 42. b = H1, 43. c = H2, 44. d = H3, 45. e = H4; 46. 47. for (var j = 0; j < 80; j++) { 48. 49. if (j < 16) w[j] = m[i + j]; 50. else { 51. var n = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16]; 52. w[j] = (n << 1) | (n >>> 31); 53. } 54. 55. var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + ( 56. j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 : 57. j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 : 58. j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 : 59. (H1 ^ H2 ^ H3) - 899497514); 60. 61. H4 = H3; 62. H3 = H2; 63. H2 = (H1 << 30) | (H1 >>> 2); 64. H1 = H0; 65. H0 = t; 66. 67. } 68. 69. H0 += a; 70. H1 += b; 71. H2 += c; 72. H3 += d; 73. H4 += e; 74. 75. } 76. 77. return [H0, H1, H2, H3, H4]; 78. 79. }; 80. 81. // Package private blocksize 82. SHA1._blocksize = 16; 83. 84. SHA1._digestsize = 20; 85. 86. })();
Crypto.js
1. if (typeof Crypto == "undefined" || ! Crypto.util) 2. { 3. (function(){ 4. 5. var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 6. 7. // Global Crypto object 8. // with browser window or with node module 9. var Crypto = (typeof window === 'undefined') ? exports.Crypto = {} : window.Crypto = {}; 10. 11. // Crypto utilities 12. var util = Crypto.util = { 13. 14. // Bit-wise rotate left 15. rotl: function (n, b) { 16. return (n << b) | (n >>> (32 - b)); 17. }, 18. 19. // Bit-wise rotate right 20. rotr: function (n, b) { 21. return (n << (32 - b)) | (n >>> b); 22. }, 23. 24. // Swap big-endian to little-endian and vice versa 25. endian: function (n) { 26. 27. // If number given, swap endian 28. if (n.constructor == Number) { 29. return util.rotl(n, 8) & 0x00FF00FF | 30. util.rotl(n, 24) & 0xFF00FF00; 31. } 32. 33. // Else, assume array and swap all items 34. for (var i = 0; i < n.length; i++) 35. n[i] = util.endian(n[i]); 36. return n; 37. 38. }, 39. 40. // Generate an array of any length of random bytes 41. randomBytes: function (n) { 42. for (var bytes = []; n > 0; n--) 43. bytes.push(Math.floor(Math.random() * 256)); 44. return bytes; 45. }, 46. 47. // Convert a byte array to big-endian 32-bit words 48. bytesToWords: function (bytes) { 49. for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8) 50. words[b >>> 5] |= (bytes[i] & 0xFF) << (24 - b % 32); 51. return words; 52. }, 53. 54. // Convert big-endian 32-bit words to a byte array 55. wordsToBytes: function (words) { 56. for (var bytes = [], b = 0; b < words.length * 32; b += 8) 57. bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); 58. return bytes; 59. }, 60. 61. // Convert a byte array to a hex string 62. bytesToHex: function (bytes) { 63. for (var hex = [], i = 0; i < bytes.length; i++) { 64. hex.push((bytes[i] >>> 4).toString(16)); 65. hex.push((bytes[i] & 0xF).toString(16)); 66. } 67. return hex.join(""); 68. }, 69. 70. // Convert a hex string to a byte array 71. hexToBytes: function (hex) { 72. for (var bytes = [], c = 0; c < hex.length; c += 2) 73. bytes.push(parseInt(hex.substr(c, 2), 16)); 74. return bytes; 75. }, 76. 77. // Convert a byte array to a base-64 string 78. bytesToBase64: function (bytes) { 79. 80. // Use browser-native function if it exists 81. if (typeof btoa == "function") return btoa(Binary.bytesToString(bytes)); 82. 83. for(var base64 = [], i = 0; i < bytes.length; i += 3) { 84. var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; 85. for (var j = 0; j < 4; j++) { 86. if (i * 8 + j * 6 <= bytes.length * 8) 87. base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F)); 88. else base64.push("="); 89. } 90. } 91. 92. return base64.join(""); 93. 94. }, 95. 96. // Convert a base-64 string to a byte array 97. base64ToBytes: function (base64) { 98. 99. // Use browser-native function if it exists 100. if (typeof atob == "function") return Binary.stringToBytes(atob(base64)); 101. 102. // Remove non-base-64 characters 103. base64 = base64.replace(/[^A-Z0-9+\/]/ig, ""); 104. 105. for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) { 106. if (imod4 == 0) continue; 107. bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2)) | 108. (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2))); 109. } 110. 111. return bytes; 112. 113. } 114. 115. }; 116. 117. // Crypto character encodings 118. var charenc = Crypto.charenc = {}; 119. 120. // UTF-8 encoding 121. var UTF8 = charenc.UTF8 = { 122. 123. // Convert a string to a byte array 124. stringToBytes: function (str) { 125. return Binary.stringToBytes(unescape(encodeURIComponent(str))); 126. }, 127. 128. // Convert a byte array to a string 129. bytesToString: function (bytes) { 130. return decodeURIComponent(escape(Binary.bytesToString(bytes))); 131. } 132. 133. }; 134. 135. // Binary encoding 136. var Binary = charenc.Binary = { 137. 138. // Convert a string to a byte array 139. stringToBytes: function (str) { 140. for (var bytes = [], i = 0; i < str.length; i++) 141. bytes.push(str.charCodeAt(i) & 0xFF); 142. return bytes; 143. }, 144. 145. // Convert a byte array to a string 146. bytesToString: function (bytes) { 147. for (var str = [], i = 0; i < bytes.length; i++) 148. str.push(String.fromCharCode(bytes[i])); 149. return str.join(""); 150. } 151. 152. }; 153. 154. })(); 155. }
CryptoMath.js
1. (function(){ 2. 3. var C = (typeof window === 'undefined') ? require('./Crypto').Crypto : window.Crypto; 4. 5. // Shortcut 6. var util = C.util; 7. 8. // Convert n to unsigned 32-bit integer 9. util.u32 = function (n) { 10. return n >>> 0; 11. }; 12. 13. // Unsigned 32-bit addition 14. util.add = function () { 15. var result = this.u32(arguments[0]); 16. for (var i = 1; i < arguments.length; i++) 17. result = this.u32(result + this.u32(arguments[i])); 18. return result; 19. }; 20. 21. // Unsigned 32-bit multiplication 22. util.mult = function (m, n) { 23. return this.add((n & 0xFFFF0000) * m, 24. (n & 0x0000FFFF) * m); 25. }; 26. 27. // Unsigned 32-bit greater than (>) comparison 28. util.gt = function (m, n) { 29. return this.u32(m) > this.u32(n); 30. }; 31. 32. // Unsigned 32-bit less than (<) comparison 33. util.lt = function (m, n) { 34. return this.u32(m) < this.u32(n); 35. }; 36. 37. })();