App接入微信登錄以及支付(多端用戶唯一標識之Unionid)

1 https://open.weixin.qq.com/ 註冊開放平臺

2 https://pay.weixin.qq.com/index.php/core/home/login? 註冊 微信商戶 認證之後 300塊

3 創建應用 ,提交審覈,通過之後會有微信支付 登錄等 接入 300塊

4 簽名工具下載地址https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk

微信 api 目錄 : https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5

小程序及app實現雙端合一的唯一標識
首先,簡單說下我遇到的問題是我們的程序調用微信小程序得到openid,然後通過openID得到用戶的唯一標識,用戶得以登錄,然而,當我們調用微信公衆號也同樣的到openid,同一用戶兩個不同的openid,不能區分是否爲同一用戶,然後發現無論調用微信小程序還是微信公衆號同一個用戶的到unionid是相同的,所以我們就用unionid來區分是否爲同一用戶。

UnionID機制說明:

如果開發者擁有多個移動應用、網站應用、和公衆帳號(包括小程序),可通過unionid來區分用戶的唯一性,因爲只要是同一個微信開放平臺帳號下的移動應用、網站應用和公衆帳號(包括小程序),用戶的unionid是唯一的。換句話說,同一用戶,對同一個微信開放平臺下的不同應用,unionid是相同的。

1、對於小程序獲取unionid:

    我們一般都是先獲取到微信的 unionid,然後再通過 unionid 去登錄自己的網站,就可以關聯到用戶在自己網站上的 user_id,但是在小程序登錄中,有時候可以獲取到 unionid,有時候獲取不到,在獲取不到 unionid 的情況下,用戶無法正常登錄網站。

原因:同一個微信開放平臺下的相同主體的 App、公衆號、小程序,如果用戶已經關注公衆號,或者曾經登錄過App或公衆號,則用戶打開小程序時,開發者可以直接通過 wx.login 獲取到該用戶UnionID,無須用戶再次授權
(解讀:用戶如果沒有登錄過app,也沒有登錄過公衆號,也沒有關注過公衆號的情況下,小程序中通過 wx.login 是獲取不到 unionid的)

所有就有兩種情況:

一般情況,用戶登錄過關聯的其他公衆號

使用 wx.login 獲取code,傳到後端,code換openid,unionId

   //1.login
   wx.login({
     success: function(data) {

       wx.request({
         url: openIdUrl,
         data: {
           code: data.code
         },
         success: function(res) {
           self.globalData.openid = res.data.openid
         },
         fail: function(res) {
           console.log('拉取用戶openid失敗,將無法正常使用開放接口等服務', res)
         }
       })

     },
     fail: function(err) {
       console.log('wx.login 接口調用失敗,將無法正常使用開放接口等服務', err)
       callback(err)
     }
   })

用戶沒有用過關聯的公衆號等

這時候 wx.login 就獲取不到 unionId 了。需要使用 wx.getUserInfo

解決思路:通過帶登錄態的 wx.getUserInfo 獲取到用戶的加密數據 encryptedData 和加密算法的初始向量iv,然後將 encryptdata、iv 以及 code傳給後端,後端再去通過接收到的encryptedData、iv以、code 以及之前的 session_key 解密出用戶的 openid、unionid 等

  wx.getUserInfo({
    withCredentials:false,
    success:(obj)=>{
     
        wx.request({
            url: openIdUrl,
            data: {
                code: data.code,
                encryptedData : obj.encryptedData,
                iv : obj.iv,
            },
            success: function(res) {
                self.globalData.openid = res.data.openid
            },
            fail: function(res) {
                console.log('拉取用戶openid失敗,將無法正常使用開放接口等服務', res)
            }
        })


    }
  })

實際項目中,需要將兩種情況整合使用

兩種方案:

第一種:( 前端判斷是否有 unionid )wx.login 向後端上傳 code 並且後端返回數據以後,前端判斷返回值中是否有 unionid 或者 unionid 是否爲 null,null 的情況下去調用帶有用戶登錄態的wx.getUserInfo(),然後再將微信返回的 encryptedData 和 iv 返回給後端,後端解密出相應的信息後再返回給前端;

第二種:( 後端判斷是否有 unionid )前端調用 wx.login(), wx.getUserInfo() ,把 code,encryptedData 和 iv 返回給後端,後端在拿到前端 code 之後去請求微信的接口拿 unionid,如果返回的 unionid 爲空,再用的 encryptedData、iv以及之前的 session_key 解密出 unionid,後端解密出相應的信息後再返回給前端

1、 (JAVA 後臺)向微信服務器發起請求附帶js_code、appId、secretkey和grant_type參數,以換取用戶的openid和session_key(會話密鑰)

用code (前端調用微信接口得到)換取 session_key,openid

這是一個 HTTPS 接口,開發者服務器使用登錄憑證 code 獲取 session_key 和 openid。
其中 session_key 是對用戶數據進行加密簽名的密鑰。爲了自身應用安全,session_key 不應該在網絡上傳輸。後臺解密用到。

接口地址:

https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

請求參數說明:

1 參數 是否必須 說明
2 appid 是 應用唯一標識,在微信開放平臺提交應用審覈通過後獲得
3 secret 是 應用密鑰AppSecret,在微信開放平臺提交應用審覈通過後獲得
4 js_code 是 填寫第一步獲取的code參數
5 grant_type 是 填authorization_code

 Map<String, String> params = new HashMap<String, String>();
 params.put("appid", APPID);
 params.put("secret", SECRET);
 params.put("js_code", "js_Code");
 params.put("grant_type", "authorization_code");
 String openidtoken = HttpClientUtil.invokeGet(https://api.weixin.qq.com/sns/jscode2session, proxy, params,
 utf-8, 60000);

返回參數:

參數 說明
openid 用戶唯一標識
session_key 會話密鑰

2、AES解密核心代碼:(包含用戶敏感信息的encryptedData信息由前端提供,後臺負責解密數據,得到unionId)

[java] view plain copy 
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {  
    initialize();  
    try {  
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");  
        Key sKeySpec = new SecretKeySpec(keyByte, "AES");  
  
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化   
        byte[] result = cipher.doFinal(content);  
        return result;  
    } catch (NoSuchAlgorithmException e) {  
        e.printStackTrace();    
    } catch (NoSuchPaddingException e) {  
        e.printStackTrace();    
    } catch (InvalidKeyException e) {  
        e.printStackTrace();  
    } catch (IllegalBlockSizeException e) {  
        e.printStackTrace();  
    } catch (BadPaddingException e) {  
        e.printStackTrace();  
    } catch (NoSuchProviderException e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    } catch (Exception e) {  
        // TODO Auto-generated catch block  
        e.printStackTrace();  
    }  

3、解密用戶信息:

byte[] resultByte = Aes.decrypt(  
                        Base64.decodeBase64(encryptedData),  
                        Base64.decodeBase64(session_key),  
                        Base64.decodeBase64(ivByte));  
  
if (null != resultByte && resultByte.length > 0) {  
    String userInfo = new String(resultByte, "UTF-8");  
    // 結果轉json  
    JsonObject jsonobject = null;  
    try {  
        JsonParser par = new JsonParser();  
        JsonElement jsonelement = par.parse(userInfo);  
        jsonobject = jsonelement.getAsJsonObject();  
    } catch (Exception e) {  
        。。。。。。  
    }  
    // 獲取unionid  
    unionID = jsonobject.get("unionId").getAsString() + "";  
}  

注:String和字節數組之間的轉換:

通過 Base64.decodeBase64(String)就可以得到字節數組。

通過 String userInfo = new String(resultByte, “UTF-8”); 就得到了想要的String

4、解密得到的結果:
加密過程微信服務器完成,解密過程在我們的服務器完成,即由 encryptData 得到如下數據:

 {
 "openId": "OPENID",
 "nickName": "NICKNAME",
 "gender": GENDER,
 "city": "CITY",
 "province": "PROVINCE",
 "country": "COUNTRY",
 "avatarUrl": "AVATARURL",
 "unionId": "UNIONID",
 "watermark":
 {
 "appid":"APPID",
 "timestamp":TIMESTAMP
 }
 }

5、把得到的unionId與用戶的唯一標識綁定在一起,通過標識就可以進行下一步操作,系統不同,操作不同,這裏不再詳談。

2、對於公衆號獲取unionId:

1、先拿code獲取網頁授權access_token以及openid

接口地址:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=JSCODE&grant_type=authorization_code

請求參數說明:

1 參數 是否必須 說明
2 appid 是 應用唯一標識,在微信開放平臺提交應用審覈通過後獲得
3 secret 是 應用密鑰AppSecret,在微信開放平臺提交應用審覈通過後獲得
4 code 是 填寫第一步獲取的code參數
5 grant_type 是 填authorization_code

 Map<String, String> params = new HashMap<String, String>();
 params.put("appid", APPID);
 params.put("secret", SECRET);
 params.put("code", "Code");
 params.put("grant_type", "authorization_code");
 String openidtoken = HttpClientUtil.invokeGet(https://api.weixin.qq.com/sns/oauth2/access_token, proxy, params,
 utf-8, 60000);


 返回參數:
 { 
 "access_token":"ACCESS_TOKEN", 
 "expires_in":7200, 
 "refresh_token":"REFRESH_TOKEN",
 "openid":"OPENID", 
 "scope":"SCOPE" 
 }
 參數             說明
 access_token    接口調用憑證
 expires_in      access_token接口調用憑證超時時間,單位(秒)
 refresh_token   用戶刷新access_token
 openid          授權用戶唯一標識
 scope           用戶授權的作用域,使用逗號(,)分隔.

2、可以看到除access_token外,還可以獲得openid,用拿到的access_token和openid獲取unionID

接口地址:

https://api.weixin.qq.com/sns/userinfo?appid=APPID&secret=SECRET&code=JSCODE&grant_type=authorization_code

請求參數說明:

1 參數 是否必須 說明
2 access_token 是 接口調用憑證
3 openid 是 授權用戶唯一標識
4 lang 否 一般爲固定值zh_CN

 Map<String, String> params = new HashMap<String, String>();
 params.put("access_token", access_token);
 params.put("openid", openId);
 params.put("lang", "zh_CN");
 String openidtoken = HttpClientUtil.invokeGet(https://api.weixin.qq.com/sns/userinfo, proxy, params,
 utf-8, 60000);

在返回值裏就包含有用戶的unionID。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章