微信小程序 获取手机号 JavaScript解密示例代码详解

刚开始开发微信小程序的时候,想着实现手机验证码登入,后来查阅资料得知,发给用户的短信是要自己付费的。后来想想,微信获取用户的手机号一样可以保证手机号码的真实性,因为手机号既然可以绑定微信,那么肯定是被严格核验过的,然后就开始了获取手机号之旅,网上教程有很多,但不知什么原因,都是会少一些内容,有的只有前端代码,没有后端;有的后端代码是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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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需要自己替换。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

<%@ 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。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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:

1

2

3

4

5

6

7

8

9

10

11

//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,解密成真正的手机号码。

我们先来看获取手机号的页面的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

(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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

(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);

};

  

})();

https://www.jb51.net/article/186483.htm

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值