官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
一.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(jscode)) {
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;
}
}