同一個微信開放平臺下的相同主體的App、公眾號虐杯、小程序的unionid是相同的玛歌,這樣可判斷是否同一用戶
微信針對不同的用戶在不同的應用下都有唯一的一個openId
每個用戶對每個公眾號的OpenID是唯一的,對于不同公眾號擎椰,同一用戶的openid不同
?例如:
????同一用戶在移動應用(網(wǎng)頁web授權)支子,APP,公眾號确憨,小程序译荞,商戶等授權或關注后瓤的,openid是不同的
? ? 因此獲取在每種應用底下獲取到openid的url地址和請求參考也是不一樣的
? ? 應用名稱? ? ? ? ?URL? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 參數(shù)
? ? 小程序? ? ? ? ? ? ?https://api.weixin.qq.com/sns/jscode2session? ? ? ? ? ? ? ? ? appid、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?secret吞歼、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?js_code=WebAuthCode圈膏、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? grant_type=authorization_code
? ? 微信網(wǎng)頁 ? ? ? ? https://api.weixin.qq.com/sns/oauth2/access_token? ? ??appid、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?secret篙骡、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?code=WebAuthCode稽坤、
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?grant_type=authorization_code
????注意:
? ? ? ? 1.這里得到的返回結(jié)果:除了openid,還有access_token
? ? ? ? 2.除了小程序外糯俗,其它應用開發(fā)(如:公眾號尿褪、web應用、商戶)獲取openid的url地址是一?樣得湘,只是appid和secret參數(shù)不同杖玲,如果是公眾號獲取openid,則參數(shù)填寫公眾號的openid和secret,同理商戶和其它應用淘正,appid和secret不同也就決定了在這個應用下的用戶對應的openid是不同的摆马。
? ? 東湖網(wǎng)上支付,采用了兩種支付方式:H5支付和公眾號支付鸿吆,都是采用“微信網(wǎng)頁”的同一URL來獲取webauthcode來交換openid囤采。這里面涉及到3個openid(用戶關注的公眾號openid,微信網(wǎng)頁移動應用openid惩淳,商戶openid)蕉毯,那不管是哪種支付,獲取的Openid都是由商戶的appid和secret參數(shù)來獲得這個用戶在商戶應用中的openid思犁。問題來了代虾,參見下面的"問題二"。
問題一:如果我們項目中有了個UserId,要能關聯(lián)同一微信平臺下的全部應用(移動應用抒倚、網(wǎng)站應用褐着、公眾帳號坷澡、小程序)托呕,怎么關聯(lián)呢?
問題二:在公眾號內(nèi)移動應用進行H5支付频敛,怎么確定是同一個用戶呢项郊?
open.weixin.qq.com的unionid機制。開放平臺和UnionId機制參考說明
使用unionid機制前提:open.weixin.qq.com中注冊為開發(fā)者且認證斟赚,綁定開發(fā)者賬號下的所有應用
如果不需要確定區(qū)分每種應用中的用戶是不是同一用戶着降,則不用理會unionid機制。?
小程序中獲取openId方法:
? ? 1.調(diào)用wx.login的時候會返回一個web authorize code
? ? 2.通過這個web authorize code去微信后臺獲取
? ? ? ? 后臺地址:https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
? ??????傳入appid,secret,JSCODE(web authorize code)去換取到openid,session_key以及unionid等信息
? ? ? ? 方式一:直接在小程序中調(diào)用(問題:https://api.weixin.qq.com 不在以下 request 合法域名列表中)
? ? ? ? ? ? 在調(diào)試階段拗军,我們可以在微信開發(fā)者工具中禁用這個警告任洞,考慮到小程序的發(fā)布版本蓄喇,
? ? ? ? ? ? 小程序中我們要設置自己的后臺合法域名,又不能同時設置兩個request合法域名? ? ? ? ? ??
? ? ? ? 方式二:通過我們自己的后臺去微信平臺URL獲取
? ? ? ? ????小程序獲取到web authorize code傳入到我們自己的后臺去獲取openId
? ? ? ? ? ? 官方:
? ? ? ? ? ? ? ? 1.小程序調(diào)用wx.login,wx.getUserInfo獲取臨時登錄憑證code交掏,并回傳到開發(fā)者服務器
? ? ? ? ? ? ? ? 2.開發(fā)者服務器以code換取用戶唯一標識openid和會話密鑰session_key
? ??????????????之后開發(fā)者服務器可以根據(jù)用戶標識來生成自定義登錄態(tài)妆偏,用于后續(xù)業(yè)務邏輯中前后端交互時識別用戶身份。具體實現(xiàn)流程如下:? ? ?
? ? 官方提醒:
????????//正常返回的JSON數(shù)據(jù)包
????????{"openid":"OPENID","session_key":"SESSIONKEY",}
????????//滿足UnionID返回條件時盅弛,返回的JSON數(shù)據(jù)包?
? ? ? ? //滿足前提:open.weixin.qq.com中注冊為開發(fā)者且認證钱骂,綁定開發(fā)者賬號下的所有應用? ? ??
????????{"openid":"OPENID","session_key":"SESSIONKEY","unionid":"UNIONID"}
????????//錯誤時返回JSON數(shù)據(jù)包(示例為Code無效)
????????{"errcode":40029,"errmsg":"invalid code"}? ??
? ? 3.獲取加密信息
? ? 如果只是拿小程序的openid和unionid,則不用考慮這步挪鹏,
????因為上面第2步就可拿到openid和unionid(滿足unionid機制)
????在調(diào)用 wx.login,wx.getUserInfo都會返回一個對象见秽,這個對象有一個屬性叫encryptedData? ? ? ? ? ??
這個字段的數(shù)據(jù)是加密的,所以我們可以通過解密的方式來得到原文信息讨盒,解密后為json解取,結(jié)構(gòu)如下:
? ?????{"openId":"OPENID","nickName":"NICKNAME","gender": GENDER,"city":"CITY","province":"PROVINCE","country":"COUNTRY","avatarUrl":"AVATARURL","unionId":"UNIONID","watermark": {"appid":"APPID","timestamp":TIMESTAMP }}
??新的數(shù)據(jù)解密方法?
? ??????總的來說還是原來的算法,還是原來的邏輯結(jié)構(gòu)返顺,不同的是解密方式肮蛹,
????????以前通過session_key得到iv,新方法是直接從前臺接口處得到iv來解密创南,所改變的也只是傳輸?shù)臄?shù)據(jù)
? ? ? ? 1.對稱解密使用的算法為 AES-128-CBC伦忠,數(shù)據(jù)采用PKCS#7填充。
? ? ? ? 2.對稱解密的目標密文為 Base64_Decode(encryptedData)稿辙。
? ? ? ? 3.對稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節(jié)昆码。
? ? ? ? 4.對稱解密算法初始向量 為Base64_Decode(iv),其中iv由數(shù)據(jù)接口返回邻储。
詳細流程:
1.小程序客戶端調(diào)用wx.login赋咽,回調(diào)里面包含js_code。
2.然后將js_code發(fā)送到服務器A(開發(fā)者服務器),服務器A向微信服務器發(fā)起請求附帶js_code吨娜、appId脓匿、secretkey和grant_type參數(shù),以換取用戶的openid和session_key(會話密鑰)宦赠。
3.服務器A拿到session_key后陪毡,生成一個隨機數(shù)我們叫3rd_session,以3rdSessionId為key,以session_key + openid為value緩存到redis或memcached中;因為微信團隊不建議直接將session_key在網(wǎng)絡上傳輸勾扭,由開發(fā)者自行生成唯一鍵與session_key關聯(lián)毡琉。其作用是:
????將3rdSessionId返回給客戶端,維護小程序登錄態(tài)妙色。
????通過3rdSessionId找到用戶session_key和openid桅滋。
4.客戶端拿到3rdSessionId后緩存到storage,
5.通過wx.getUserIinfo可以獲取到用戶敏感數(shù)據(jù)encryptedData 身辨。
6.客戶端將encryptedData丐谋、3rdSessionId和偏移量一起發(fā)送到服務器A
7.服務器A根據(jù)3rdSessionId從緩存中獲取session_key
8.在服務器A使用AES解密encryptedData芍碧,從而實現(xiàn)用戶敏感數(shù)據(jù)解密
重點在6、7号俐、8三個環(huán)節(jié)师枣。
服務器端解密代碼示例
package com.iups.wx.wxservice;
importjava.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
importjava.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
importjava.security.NoSuchAlgorithmException;
importjava.security.NoSuchProviderException;
import java.security.Security;
importjava.security.spec.InvalidParameterSpecException;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
importjavax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.sf.json.JSONObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.codehaus.xfire.util.Base64;
importcom.iups.wx.common.RemoteInterfaceAddress;
import com.iups.wx.util.HttpsClientUtil;
/**
?*微信小程序信息獲取
?*@author Administrator
?*@Date 2017年2月16日11:56:08
?*/
public class WXAppletUserInfo {
???/**
????*獲取微信小程序 session_key 和openid
????* @param code
????* @return
????*/
???public JSONObject getSessionKeyOropenid(String code){
???????//微信端登錄code值
???????String wxCode = code;
???????String requestUrl = RemoteInterfaceAddress.GETSESSIONKEYOROPENID;
???????Map requestUrlParam = newHashMap();
???????requestUrlParam.put("appid", RemoteInterfaceAddress.AppletAPPID);
???????requestUrlParam.put("secret",RemoteInterfaceAddress.AppletAppSecret);
???????requestUrlParam.put("js_code", wxCode);
???????requestUrlParam.put("grant_type","authorization_code");
???????JSONObject jsonObject = HttpsClientUtil.getInstance().sendGetRequest(requestUrl,requestUrlParam);
???????return jsonObject;
??? }
???/**
????*解密用戶敏感數(shù)據(jù)獲取用戶信息
????* @param sessionKey數(shù)據(jù)進行加密簽名的密鑰
????* @param encryptedData包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù)
????* @param iv加密算法的初始向量
????* @return
????*/
???public JSONObject getUserInfo(String encryptedData,StringsessionKey,String iv){
???????//被加密的數(shù)據(jù)
???????byte[] dataByte = Base64.decode(encryptedData);
???????//加密秘鑰
???????byte[] keyByte = Base64.decode(sessionKey);
???????//偏移量
???????byte[] ivByte = Base64.decode(iv);
???????try {
?????????????? //如果密鑰不足16位,那么就補足.? 這個if 中的內(nèi)容很重要
???????????int base = 16;
???????????if (keyByte.length % base != 0) {
??????????????? int groups = keyByte.length /base + (keyByte.length % base != 0 ? 1 : 0);
??????????????? byte[] temp = new byte[groups *base];
??????????????? Arrays.fill(temp, (byte) 0);
??????????????? System.arraycopy(keyByte, 0,temp, 0, keyByte.length);
??????????????? keyByte = temp;
???????????}
???????????//初始化
???????????Security.addProvider(newBouncyCastleProvider());
???????????Cipher cipher =Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
???????????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 = newString(resultByte, "UTF-8");
??????????????? returnJSONObject.fromObject(result);
???????????}
???????} catch (NoSuchAlgorithmException e) {
???????????e.printStackTrace();
???????} catch (NoSuchPaddingException e) {
???????????e.printStackTrace();
???????} catch (InvalidParameterSpecException e) {
???????????e.printStackTrace();
???????} catch (IllegalBlockSizeException e) {
???????????e.printStackTrace();
???????} catch (BadPaddingException e) {
???????????e.printStackTrace();
???????} catch (UnsupportedEncodingException e) {
???????????e.printStackTrace();
???????} catch (InvalidKeyException e) {
???????????e.printStackTrace();
???????} catch (InvalidAlgorithmParameterException e) {
???????????e.printStackTrace();
???????} catch (NoSuchProviderException e) {
???????????e.printStackTrace();
???????}
???????return null;
??? }
}
: