微信小程序获取手机号授权完整实现
一.app.js配置
App({ onLaunch: function () { }, onShow: function () { }, onHide: function () { // console.log(getCurrentPages()) }, onError: function (msg) { //console.log(msg) }, globalData: { userInfo: null, appid: "", appsecret: "", phoneNumber: '', getSessionKeyUrl: 'http://localhost:8888/test/getsessionkey', getPhoneUrl: 'http://localhost:8888/test/getphone', orderHomeUrl: 'http://localhost:8888/login?phone=', driverLoginUrl: 'http://localhost:8888/driver/login' } });
二.app.json配置
{ "pages": [ "pages/login/login", "pages/main/main", "pages/driverlogin/driverlogin" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#F8F8F8", "navigationBarTitleText": "小程序", "navigationBarTextStyle": "black" }, "sitemapLocation": "sitemap.json" }
三.微信授权
1.页面
<view wx:if="{{isShow}}" > <view class="container" style="padding:0rpx"> <image src='./login.png' style="z-index:-99;width:100%;height:100%;" mode="widthFix"></image> <view style="margin-top:10rpx; width: 98%;" > <button class="show" type="primary" lang="zh_CN" open-type='getPhoneNumber' bindgetphonenumber="getPhoneNumber">{{wechat}}</button> </view> </view> </view> <view class="container" wx:else></view>
2.页面样式
.show{ display: block; border-radius: 8rpx; margin: 20rpx 20rpx 20rpx 20rpx; font-size: 35rpx; } .container{ position: fixed; /*关键属性,设置为fixed*/ display: flex; flex-direction: column; width: 100%; height: 100%; }
3.json
{ "usingComponents": {} }
4.js
const app = getApp(); Page({ data: { // 判断小程序的API,回调,参数,组件等是否在当前版本可用。 canIUse: wx.canIUse('button.open-type.getPhoneNumber'), wechat: '微信快捷登录', isShow: false }, onLoad: function() { // 从缓存中取手机号 console.log("获取手机号!") try { var value = wx.getStorageSync('phoneNumber') if (value) { // 说明已登录 跳转 页面 console.log("获取缓存:"+value) wx.navigateTo({ url: '../main?param=' + value }) }else{// 未登录 显示 微信授权页面 this.setData({ isShow: true }) } } catch (e) { } // 解决第一次获取手机号失败问题 wx.login({ success: res => { if(res.code){ console.log("code->", res.code) } } }) }, // 0.获取手机号授权 getPhoneNumber: function(e) { // 用户拒绝授权 if(e.detail.errMsg == "getPhoneNumber:fail user deny") { wx.showToast({ icon: "none", title: '请允许获取手机号,否则功能不可用!', }) return } /// 用户允许授权 console.log("iv->", e.detail.iv); //包括敏感数据在内的完整用户信息的加密数据,需要解密 console.log("encryptedData->", e.detail.encryptedData); //加密算法的初始向量,解密需要用到 /// 获取手机号 // 1.获取临时登录凭证code wx.login({ success: res => { if(res.code){ this.code = res.code; console.log("code->", res.code) this.getSessionKey(res.code, e.detail.encryptedData, e.detail.iv); } } }) }, // 2.访问登录凭证校验接口获取session_key(后续改成后台实现) getSessionKey: function(js_code, encryptedData, iv) { wx.request({ url: app.globalData.getSessionKeyUrl, data: { 'jscode': js_code, 'sign': 'sign' }, method: 'GET', header: { 'content-type': 'application/json' }, // 设置请求的 header success: function(data) { console.log("session_key->", data.data) if(data.data==undefined){ wx.showToast({ icon: "none", title: 'session_key获取失败,请重新登录!', }) return } // 3. 解密获取手机号 wx.request({ url: app.globalData.getPhoneUrl, data: { 'encryptedData': encodeURIComponent(encryptedData),//需要进行编码 'iv': iv, 'sessionKey': data.data, 'sign': 'sign', }, method: 'GET', header: { 'content-type': 'application/json' }, // 设置请求的 header success: function(data2) { console.log(data2.data.phoneNumber) if(data2.statusCode == 200) { if(data2.data.phoneNumber==undefined){ // 获取手机号失败 跳转到 常规 用户登录页面(通过webview) wx.navigateTo({ url: '../driverlogin/driverlogin' }) return } // 存储数据到缓存 wx.setStorage({ key:"phoneNumber", data:data2.data.phoneNumber }) // 4.跳转web-view页面 wx.navigateTo({ url: '../main?param=' + data2.data.phoneNumber }) // 4 } }, fail: function(err) { console.log(err); wx.showToast({ icon: "none", title: '获获取手机号失败,请重试!', }) } })// 3 }, fail: function(err) { console.log(err); wx.showToast({ icon: "none", title: 'session_key获取失败,请重新登录!', }) return } }) }// 2 })
4.后端实现
1)获取sessionkey
@GetMapping("getsessionkey") @ResponseBody public synchronized Object getSessionKey(String jscode,String sign) { MyResult res = new MyResult(); if(StringUtils.isBlank(checkcode)) { res.setCode(-1); res.setMsg("鉴权失败!"); return res; } if(!checkcode.equals("sign")) { res.setCode(-1); res.setMsg("鉴权失败!"); return res; } if(StringUtils.isBlank(sign)||checkcode.equals("undefined")) { res.setCode(-1); res.setMsg("无效参数!"); return res; } OkHttpClient client = new OkHttpClient().newBuilder() .build(); String requestStr = miniProgramConfig.getSessionkeyurl() +"?appid="+miniProgramConfig.getAppid() +"&secret="+miniProgramConfig.getSecret() +"&js_code="+jscode +"&grant_type=authorization_code"; Request request = new Request.Builder() .url(requestStr) .method("GET", null) .build(); try { Response response = client.newCall(request).execute(); String responseStr = response.body().string(); JSONObject jsonObject = JSONObject.parseObject(responseStr); // 获取到session_key String session_key = jsonObject.getString("session_key"); if(StringUtils.isBlank(session_key)) { res.setCode(-1); res.setMsg("获取sessionkey失败!"); return res; } return session_key; } catch (IOException e) { logger.error("encryptedData,decode失败!", e); res.setCode(-1); res.setMsg("获取sessionkey失败!"); return res; } }
2)获取解密手机号
@GetMapping("getphone") @ResponseBody public synchronized Object getPhone(String encryptedData, String iv, String sessionKey,String sign) { MyResult res = new MyResult(); if(StringUtils.isBlank(sign)) { res.setCode(-1); res.setMsg("鉴权失败!"); return res; } if(!sign.equals("sign")) { res.setCode(-1); res.setMsg("鉴权失败!"); return res; } if(StringUtils.isBlank(encryptedData) ||encryptedData.equals("undefined") ||StringUtils.isBlank(iv) ||iv.equals("undefined") ||StringUtils.isBlank(sessionKey) ||sessionKey.equals("undefined")) { res.setCode(-1); res.setMsg("参数错误!"); return res; } // 解码 try { encryptedData = URLDecoder.decode(encryptedData,"UTF-8"); } catch (UnsupportedEncodingException e) { res.setCode(-1); res.setMsg("encryptedData,decode失败!"); logger.error("encryptedData,decode失败!", e); return res; } return WechatDecryptDataUtil.decryptData(encryptedData, sessionKey, iv); }
3)解密工具类
import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Base64; import com.zit.tis.wechat.miniproject.MiniProgramController; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Key; import java.security.Security; /** * 微信工具类 */ public class WechatDecryptDataUtil { private static Logger logger = LogManager.getLogger(WechatDecryptDataUtil.class); public static void main(String[] args) { String result = decryptData( "111==", "222==", "333==" ); System.out.println("result = " + result); } public synchronized static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) { String res = null; try { res = new String( decryptOfDiyIV( Base64.decode(encryptDataB64), Base64.decode(sessionKeyB64), Base64.decode(ivB64) ) ); } catch (Exception e) { logger.error("encryptDataB64:"+encryptDataB64+"\n"+"sessionKeyB64:"+sessionKeyB64+"\n"+"ivB64:"+ivB64); } return res; } private static final String KEY_ALGORITHM = "AES"; private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding"; private static Key key; private static Cipher cipher; private static void init(byte[] keyBytes) { // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要 int base = 16; if (keyBytes.length % base != 0) { int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0); byte[] temp = new byte[groups * base]; Arrays.fill(temp, (byte) 0); System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length); keyBytes = temp; } // 初始化 Security.addProvider(new BouncyCastleProvider()); // 转化成JAVA的密钥格式 key = new SecretKeySpec(keyBytes, KEY_ALGORITHM); try { // 初始化cipher cipher = Cipher.getInstance(ALGORITHM_STR, "BC"); } catch (Exception e) { e.printStackTrace(); } } /** * 解密方法 * * @param encryptedData 要解密的字符串 * @param keyBytes 解密密钥 * @param ivs 自定义对称解密算法初始向量 iv * @return 解密后的字节数组 */ private static byte[] decryptOfDiyIV(byte[] encryptedData, byte[] keyBytes, byte[] ivs) { byte[] encryptedText = null; init(keyBytes); try { cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs)); encryptedText = cipher.doFinal(encryptedData); } catch (Exception e) { e.printStackTrace(); } return encryptedText; } }