環境
後端: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.返回結果