本篇记录说明
微信小程序获取用户手机号码,Java后台servlet解密
(第一次写博客,写得不好的地方见谅,面向新手,大佬请无视,不喜勿喷)
一、前言:
微信小程序有一个获取用户手机号码很便捷的接口,通过getPhoneNumber获取用户的已经绑定微信的手机号码。
现在微信和注重用户体验,必须要用户主动触发才可以。必须使用***open-type="getPhoneNumber" ***的按钮来触发。
二、实现步骤:
1、通过wx.login获取【code】;
2、通过getPhoneNumber获取【encryptedData】 、【iv】;(ps:切记第1步和第2步的顺序不能颠倒,先调用wx.login,把wx.login写在onload方法里面;然后再通过getPhoneNumber获取【encryptedData】 、【iv】)
3、通过wx.request将【encryptedData】 、【iv】 、【code】发送到Java后台;
4、Java后台使用【code】,请求微信后台登录凭证校验接口auth.code2Session,获取【openid】和【sessionKey】返回Java后台;
5、Java后台使用【encryptedData】、【iv】、【sessionKey】解密获取用户手机号返回给微信小程序。(ps:微信给的后台解密示例代码里面没有Java,是的,你没有看错,没有Java。)
三、代码展示
1、小程序代码如下:
(1)getPhoneNumber.wxml
<view class='header'>
</view>
<view class='text_view'>
<text>申请获取以下权限</text>
</view>
<view class='content'>
<text>获得您的信息(手机号码等)</text>
</view>
<button class='bottom' type='primary' lang="zh_CN" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
获取手机号码
</button>
<view class='problem'>
<text>登陆遇到问题?点此联系客服</text>
<button class='contact-btn' open-type='contact'>a</button>
</view>
<view bindtap="cooperate" class='cooperate-phonenumber'>
</view>
(2)getPhoneNumber.wxss
page {
/* background-color: #eee; */
height: 100%;
overflow: hidden;
}
.header {
margin: 90rpx 0 90rpx 50rpx;
text-align: center;
width: 650rpx;
height: 300rpx;
line-height: 450rpx;
}
.header image {
width: 180rpx;
height: 180rpx;
}
.text_view {
width: 750rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.content {
margin-bottom: 90rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.content text {
display: block;
color: #9d9d9d;
margin-top: 40rpx;
}
.bottom {
border-radius: 80rpx;
margin: 30rpx 20rpx;
font-size: 35rpx;
}
.problem {
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
margin-left: 50%;
}
.problem text {
font-size: 28rpx;
/* display: block; */
color: #576B95;
}
.contact-btn {
position: absolute;
width: 100%;
opacity: 0;
}
.cooperate-phonenumber {
width: 100%;
position: fixed;
bottom:20rpx;
display: flex;
/* margin-top: 30%; */
flex-direction: column;
justify-content: center;
align-items:center;
/* margin-left: 50%; */
}
.cooperate-phonenumber text {
font-size: 28rpx;
/* display: block; */
color: #576B95;
}
(3)getPhoneNumber.js
// src/pages/getPhoneNumber/getPhoneNumber.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
//获取openid
wx.login({
success(res) {
//获取登录凭证
console.log("res.conde:" + res.code)
try {
wx.setStorageSync('code', res.code)
} catch (e) {}
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
getPhoneNumber(e) {
console.log(e)
console.log("e.detail.errMsg:" + e.detail.errMsg)
console.log("e.detail.iv:" + e.detail.iv)
console.log("e.detail.encryptedData:" + e.detail.encryptedData)
var code = wx.getStorageSync('code')
console.log("conde:" + code)
wx.request({
url: 'java后台servlet链接',
//这里要改成post方法,很危险,用get方法的话_20_2_1
method: "POST",
data: {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
code: code,
},
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
success: function(data) {
console.log('data:' + data.data)
},
fail: function() {
console.log('request请求错误')
},
})
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
}
})
2、Java后台servlet代码如下:
(1) getPhoneNumberServlet,网络请求用okhttp3
package servlet;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import javax.security.sasl.SaslException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import tools.WXCore;
public class getPhoneNumberServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String selStr;
InputStream is = null;
try {
is = request.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) {
sb.append(new String(b, 0, n));
}
selStr = java.net.URLDecoder.decode(sb.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("selStr:" + selStr);
String encryptedData = selStr.substring(selStr.lastIndexOf("encryptedData=") + 14, selStr.lastIndexOf("&iv="));
System.out.println("encryptedData:" + encryptedData);
String iv = selStr.substring(selStr.lastIndexOf("iv=") + 3, selStr.lastIndexOf("&code="));
System.out.println("iv:" + iv);
String code = selStr.substring(selStr.lastIndexOf("code=") + 5);
System.out.println("code:" + code);
OkHttpClient client = new OkHttpClient();
Request okrequest = new Request.Builder()
.url("https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code")
.build();
try {
Response okresponse = client.newCall(okrequest).execute();
String responsedata = okresponse.body().string();
System.out.println("responsedata:" + responsedata);
String openid = responsedata.substring(responsedata.lastIndexOf(":") + 2,
responsedata.lastIndexOf("\""));
String session_key = responsedata.substring(responsedata.indexOf(":") + 2,
responsedata.indexOf("==") + 2);
System.out.println("原始的openid:" + openid);
System.out.println("原始的session_key:" + session_key);
String appId = "wx7xxxxxxxxxxxxxxxxxxxx";
WXCore wxcore = new WXCore();
String phonenumber = wxcore.decrypt(appId, encryptedData, responsedata_session_key, iv);
System.out.println("手机号码:" + phonenumber);
response.getWriter().write(openid + "&" + phonenumber);
} catch (IOException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
(2) AES类、WxPKCS7Encoder类、WXCore类,用于解密,用到的包有:bcprov-jdk15on-1.52.jar,commons-codec-1.6.jar,fastjson-1.2.56.jar
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* AES加密
*/
public class AES {
public static boolean initialized = false;
/**
* AES解密
*
* @param content
* 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void initialize() {
if (initialized)
return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
// 生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* 微信小程序加解密
*/
public class WxPKCS7Encoder {
private static final Charset CHARSET = Charset.forName("utf-8");
private static final int BLOCK_SIZE = 32;
/**
* 获得对明文进行补位填充的字节.
*
* @param count 需要进行填充补位操作的明文字节个数
* @return 补齐用的字节数组
*/
public static byte[] encode(int count) {
// 计算需要填充的位数
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 获得补位所用的字符
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted 解密后的明文
* @return 删除补位字符后的明文
*/
public static byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
/**
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
*
* @param a 需要转化的数字
* @return 转化得到的字符
*/
public static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}
import org.apache.commons.codec.binary.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* 封装对外访问方法
*/
public class WXCore {
private static final String WATERMARK = "watermark";
private static final String APPID = "appid";
/**
* 解密数据
*
* @return
* @throws Exception
*/
public static String decrypt(String appId, String encryptedData, String sessionKey, String iv) {
String result = "";
try {
AES aes = new AES();
byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey),
Base64.decodeBase64(iv));
if (null != resultByte && resultByte.length > 0) {
result = new String(WxPKCS7Encoder.decode(resultByte));
JSONObject jsonObject = JSON.parseObject(result);
String decryptAppid = jsonObject.getJSONObject(WATERMARK).getString(APPID);
if (!appId.equals(decryptAppid)) {
result = "";
}
}
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return result;
}
}
四、步骤详解:
1、通过wx.login获取【code】;
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
//获取openid
wx.login({
success(res) {
//获取登录凭证
console.log("res.conde:" + res.code)
try {
wx.setStorageSync('code', res.code)
} catch (e) {}
}
})
},
2、通过getPhoneNumber获取【encryptedData】 、【iv】;(ps:切记第1步和第2步的顺序不能颠倒,先调用wx.login,把wx.login写在onload方法里面;然后再通过getPhoneNumber获取【encryptedData】 、【iv】)
getPhoneNumber(e) {
console.log(e)
console.log("e.detail.errMsg:" + e.detail.errMsg)
console.log("e.detail.iv:" + e.detail.iv)
console.log("e.detail.encryptedData:" + e.detail.encryptedData)
},
3、通过wx.request将【encryptedData】 、【iv】 、【code】发送到Java后台;
getPhoneNumber(e) {
console.log(e)
console.log("e.detail.errMsg:" + e.detail.errMsg)
console.log("e.detail.iv:" + e.detail.iv)
console.log("e.detail.encryptedData:" + e.detail.encryptedData)
var code = wx.getStorageSync('code')
console.log("conde:" + code)
wx.request({
url: 'Java后台servlet链接',
method: "POST",
data: {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
code: code,
},
header: {
"Content-Type": "application/x-www-form-urlencoded"
},
success: function(data) {
console.log('data:' + data.data)
},
fail: function() {
console.log('request请求错误')
},
})
},
4、Java后台使用【code】,请求微信后台登录凭证校验接口auth.code2Session,获取【openid】和【sessionKey】返回Java后台;(ps:本篇中不用到【openid】,后续微信小程序调用微信支付时用到)
(1)新建getPhoneNumberServlet类,获取【encryptedData】 、【iv】 、【code】
package servlet;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import javax.security.sasl.SaslException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import tools.WXCore;
public class getPhoneNumberServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String selStr;
InputStream is = null;
try {
is = request.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) {
sb.append(new String(b, 0, n));
}
selStr = java.net.URLDecoder.decode(sb.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("selStr:" + selStr);
String encryptedData = selStr.substring(selStr.lastIndexOf("encryptedData=") + 14, selStr.lastIndexOf("&iv="));
System.out.println("encryptedData:" + encryptedData);
String iv = selStr.substring(selStr.lastIndexOf("iv=") + 3, selStr.lastIndexOf("&code="));
System.out.println("iv:" + iv);
String code = selStr.substring(selStr.lastIndexOf("code=") + 5);
System.out.println("code:" + code);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
(2)将【code】、【小程序 appId】、【小程序 appSecret】、作为参数,使用okhttp请求微信后台登录凭证校验接口auth.code2Session,获取获取【openid】和【sessionKey】。(ps:本篇中不用到【openid】,后续微信小程序调用微信支付时用到)
package servlet;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import javax.security.sasl.SaslException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import tools.WXCore;
public class getPhoneNumberServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String selStr;
InputStream is = null;
try {
is = request.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) {
sb.append(new String(b, 0, n));
}
selStr = java.net.URLDecoder.decode(sb.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("selStr:" + selStr);
String encryptedData = selStr.substring(selStr.lastIndexOf("encryptedData=") + 14, selStr.lastIndexOf("&iv="));
System.out.println("encryptedData:" + encryptedData);
String iv = selStr.substring(selStr.lastIndexOf("iv=") + 3, selStr.lastIndexOf("&code="));
System.out.println("iv:" + iv);
String code = selStr.substring(selStr.lastIndexOf("code=") + 5);
System.out.println("code:" + code);
OkHttpClient client = new OkHttpClient();
Request okrequest = new Request.Builder()
.url("https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code")
.build();
try {
Response okresponse = client.newCall(okrequest).execute();
String responsedata = okresponse.body().string();
System.out.println("responsedata:" + responsedata);
String openid = responsedata.substring(responsedata.lastIndexOf(":") + 2,
responsedata.lastIndexOf("\""));
String session_key = responsedata.substring(responsedata.indexOf(":") + 2,
responsedata.indexOf("==") + 2);
System.out.println("原始的openid:" + openid);
System.out.println("原始的session_key:" + session_key);
} catch (IOException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3、调用WXCore类的decrypt方法,将【appId】、【iv】、【sessionKey】作为参数,对【encryptedData】进行解密,获取手机号码。
package servlet;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import javax.security.sasl.SaslException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import tools.WXCore;
public class getPhoneNumberServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String selStr;
InputStream is = null;
try {
is = request.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) {
sb.append(new String(b, 0, n));
}
selStr = java.net.URLDecoder.decode(sb.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("selStr:" + selStr);
String encryptedData = selStr.substring(selStr.lastIndexOf("encryptedData=") + 14, selStr.lastIndexOf("&iv="));
System.out.println("encryptedData:" + encryptedData);
String iv = selStr.substring(selStr.lastIndexOf("iv=") + 3, selStr.lastIndexOf("&code="));
System.out.println("iv:" + iv);
String code = selStr.substring(selStr.lastIndexOf("code=") + 5);
System.out.println("code:" + code);
OkHttpClient client = new OkHttpClient();
Request okrequest = new Request.Builder()
.url("https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code")
.build();
try {
Response okresponse = client.newCall(okrequest).execute();
String responsedata = okresponse.body().string();
System.out.println("responsedata:" + responsedata);
String openid = responsedata.substring(responsedata.lastIndexOf(":") + 2,
responsedata.lastIndexOf("\""));
String session_key = responsedata.substring(responsedata.indexOf(":") + 2,
responsedata.indexOf("==") + 2);
System.out.println("原始的openid:" + openid);
System.out.println("原始的session_key:" + session_key);
String appId = "wx7xxxxxxxxxxxxxxxxxxxx";
WXCore wxcore = new WXCore();
String phonenumber = wxcore.decrypt(appId, encryptedData, responsedata_session_key, iv);
System.out.println("手机号码:" + phonenumber);
response.getWriter().write(openid + "&" + phonenumber);
} catch (IOException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
4、大功告成。
五、运行效果截图:
六、结语
以上就是微信小程序获取用户手机号码,Java后台servlet解密的流程和注意事项,第一次写博客,写得不好的地方见谅,面向新手,大佬请无视,不喜勿喷。
如果没有解决您的问题,可以添加微信交流:very2cc