环境
后端:java
简介
小程序官方API URL: https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html
小程序登录 流程图
-------------------------------------------------------------------------------实测开始-------------------------------------------------------------------------
小程序客户端(实测)
//index.js
//获取应用实例
const app = getApp()
Page({
getPhoneNumber: function (e) {
console.log("errMsg="+e.detail.errMsg)
console.log("iv="+e.detail.iv)
console.log("encryptedData="+e.detail.encryptedData)
if (e.detail.errMsg == 'getPhoneNumber:fail user deny') {
wx.showModal({
title: '提示',
showCancel: false,
content: '未授权',
success: function (res) { }
})
} else {
wx.showModal({
title: '提示',
showCancel: false,
content: '同意授权',
success: function (res) {
wx.request({
url: 'http://xxxxxxx/aglie/user/myhuser/v1/appletLogin',
method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
data: {
code: app.globalData.codeInfo,//获取openid的话 需要向后台传递code,利用code请求api获取openid
encryptedData: e.detail.encryptedData,//获取encryptedData
iv: e.detail.iv, //获取iv
type:'1',
},
header: {
//'content-type': 'application/json' // 默认值
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function (res) {
console.log("res::=" + res.data);
}
})
}
})
}
},
getUserInfo: function(e) {
wx.request({
url: 'http://xxxxxxx/aglie/user/myhuser/v1/appletLogin',
method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
data: {
code: app.globalData.codeInfo,//获取openid的话 需要向后台传递code,利用code请求api获取openid
encryptedData: e.detail["encryptedData"],//获取encryptedData
iv: e.detail["iv"], //获取iv
type:'0',
},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success:function(res){
console.log("res::=" + res.data);
}
})
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
}
})
java服务端
一.定义final变量
// AppID(小程序ID) 小程序唯一标识 (在微信小程序管理后台获取)
private static final String APPID = "wx8a503ea211d48xxx";
// 小程序的 AppSecret(小程序密钥) (在微信小程序管理后台获取)
private static final String APPSECRET = "d3455f3cfb9685c602b60e48caa0xxxx";
// 授权(必填) 填写为 authorization_code
private static final String GRANT_TYPE = "authorization_code";
二.调用官方API,返回客户端OpenId信息 (实测)
1.API请求参数设计
参数名称 | 必选 | 类型 | 描述 |
type | true | String | 0获取用户基本信息 1获取用户电话号码 |
code | true | String | 小程序code |
iv | String | 加密算法的初始向量 | |
encryptedData | String | 加密数据 | |
注意(此处使用 encryptedData 加密与iv偏移量)加密解密处理 |
1.API 调用
/**
* @throws Exception
*
* @Title: appletLogin
* @Description: TODO(根据小程序客户端code,返回登录状态) 采用顺序方式:1.code 2.用户INFO 2.Phone
* @param: code 客户端code
* @param: encryptedData 包括敏感数据在内的完整用户信息的加密数据
* @param: iv 加密算法的初始向量
* @param: type 0用户基础信息 1用户电话号码
*
* @return: Map<String,Object>
* @throws
*/
@ResponseBody
@RequestMapping(value = "v1/appletLogin" , method = RequestMethod.POST)
public Map<String, Object> appletLogin(HttpServletRequest request,HttpServletResponse response
,@RequestParam(required=true) String code
,String encryptedData,String iv,String type) throws Exception{
Map<String, Object> map = Maps.newHashMap();
Map<String, Object> mapListInfo= Maps.newHashMap();
//code获取参数
if (StringUtils.isBlank(code)) {
map.put("status", "fail");
map.put("message", "code is not null.");
return map;
}
//定义变量
String str_code ="";
String str_encryptedData ="";
String str_iv ="";
String str_session_key= "";
String str_status= "";
String str_message="";
String str_openId="";
String str_nickName= "";
String str_gender= "";
String str_city= "";
String str_province= "";
String str_country= "";
String str_avatarUrl= "";
String str_phoneNumber= "";
String str_purePhoneNumber= "";
String str_countryCode= "";
//获取客户端请求code
str_code =request.getParameter("code");
str_encryptedData =request.getParameter("encryptedData");
str_iv =request.getParameter("iv");
//1.code请求腾讯API(返回openid,session_key)
TenApplet tenApplet=new TenApplet();
Map<String, Object> map_vale =tenApplet.get_openid_key(str_code);
str_openId= (String) map_vale.get("openId");
str_session_key= (String)map_vale.get("session_key");
str_status= (String)map_vale.get("status");
str_message= (String)map_vale.get("message");
//处理异常 code
switch (str_status) {
case "40163":
map.put("status", str_status);
map.put("message", str_message);
return map;
case "40029":
map.put("status", str_status);
map.put("message", str_message);
return map;
case "40125":
map.put("status", str_status);
map.put("message", str_message);
return map;
}
//1.1 调用AES解密(返回 encryptedData 数据) 获取用户信息和电话号码
if(StringUtils.isNotBlank(str_encryptedData)){
Map<String, Object> aes_map_vale =tenApplet.get_encryptedData(str_encryptedData, str_session_key, str_iv,type);
if("0".equals(type)){
//获取用户信息
str_nickName= (String) aes_map_vale.get("nickName");
str_gender= (String)aes_map_vale.get("gender");
str_city= (String)aes_map_vale.get("city");
str_province= (String)aes_map_vale.get("province");
str_country= (String)aes_map_vale.get("country");
str_avatarUrl= (String)aes_map_vale.get("avatarUrl");
}else if ("1".equals(type)) {
//获取电话号码
str_phoneNumber= (String) aes_map_vale.get("phoneNumber");
str_purePhoneNumber= (String)aes_map_vale.get("purePhoneNumber");
str_countryCode= (String)aes_map_vale.get("countryCode");
}
}
/**处理登录**/
成功返回
失败返回
/**处理登录 end**/
map.put("status", "success");
map.put("message", "登录成功");
return map;
}
}
2.get_openid_key()方法
//--------根据code获取 openid,session_key------------
public Map<String, Object> get_openid_key(String code) {
Map<String, Object> map = Maps.newHashMap();
//code获取参数
if (StringUtils.isBlank(code)) {
map.put("status", "fail");
map.put("message", "code is not null.");
return map;
}
//发起GET请求获取凭证
String stringToken = String.format(
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=%s",
APPID, APPSECRET, code,GRANT_TYPE);
String result = HttpRequest.sendGet(stringToken,null);
JSONObject jsonObject = null;
jsonObject = JSONObject.fromObject(result);
try {
String openId = jsonObject.getString("openid");// 用户唯一标识
String session_key = jsonObject.getString("session_key");// 密钥
map.put("openId", openId);
map.put("session_key", session_key);
map.put("status", "success");
map.put("message", "请求成功");
} catch (Exception e) {
switch (jsonObject.getString("errcode")) {
case "40163":
map.put("message", "code重复使用");
break;
case "40029":
map.put("message", "code无效");
break;
case "40125":
map.put("message", "appId or secret无效");
break;
default:
map.put("message", "错误");
break;
}
map.put("openId", "");
map.put("session_key", "");
map.put("status", "fail");
}
return map;
}
3.get_encryptedData()方法
//--------解密 ------------
/**
*
* @Title: get_encryptedData
* @Description: TODO(解密方法)
* @param: @param encryptedData
* @param: @param session_key
* @param: @param iv
* @param: @param type 0基础用户信息 1电话
* @param: @return
* @param: @throws Exception
* @return: Map<String,Object>
* @throws
*/
public Map<String, Object> get_encryptedData(String encryptedData, String session_key, String iv,String type) throws Exception {
Map<String, Object> map = Maps.newHashMap();
//解密encryptedData,获取unionId相关信息
String str_encryptedData = decrypt(encryptedData, session_key, iv,"UTF-8");
JSONObject userInfoJSON = null;
userInfoJSON = JSONObject.fromObject(str_encryptedData);
map = Maps.newHashMap();
//type 0返回用户基础信息 1返回用户电话号码
if("0".equals(type)){
map.put("openId", userInfoJSON.get("openId"));
map.put("nickName", userInfoJSON.get("nickName"));
map.put("gender", userInfoJSON.get("gender").toString());
map.put("city", userInfoJSON.get("city"));
map.put("province", userInfoJSON.get("province"));
map.put("country", userInfoJSON.get("country"));
map.put("avatarUrl", userInfoJSON.get("avatarUrl"));
map.put("language", userInfoJSON.get("language"));
}else {
map.put("phoneNumber", userInfoJSON.get("phoneNumber"));
map.put("purePhoneNumber", userInfoJSON.get("purePhoneNumber"));
map.put("countryCode", userInfoJSON.get("countryCode"));
}
return map;
}
4.秘钥处理
/**
* AES解密用户敏感数据获取用户信息
*
* @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
* @param key 数据进行加密签名的密钥
* @param iv 加密算法的初始向量
* @param encodingFormat 解密后的结果需要进行的编码
* 描述:对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
对称解密的目标密文为 Base64_Decode(encryptedData)。
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
* @return
* */
public static String decrypt(String encryptedData, String key, String iv, String encodingFormat) throws Exception {
init();
// 被加密的数据
byte[] dataByte = Base64.decodeBase64(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decodeBase64(key);
// 偏移量
byte[] ivByte = Base64.decodeBase64(iv);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, encodingFormat);
return result;
}
return null;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidParameterSpecException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private static boolean hasInited = false;
public static void init() {
if (hasInited) {
return;
}
Security.addProvider(new BouncyCastleProvider());
hasInited = true;
}
3.返回结果