第十一节:OpenId、UnionId、Session_key

同一个微信开放平台下的相同主体的App、公众号、小程序的unionid是相同的,这样可判断是否同一用户
微信针对不同的用户在不同的应用下都有唯一的一个openId
每个用户对每个公众号的OpenID是唯一的,对于不同公众号,同一用户的openid不同
 例如:
    同一用户在移动应用(网页web授权),APP,公众号,小程序,商户等授权或关注后,openid是不同的
    因此获取在每种应用底下获取到openid的url地址和请求参考也是不一样的
    应用名称         URL                                                                                              参数
    小程序             https://api.weixin.qq.com/sns/jscode2session                  appid、
                                                                                                                                     secret、
                                                                                                                                     js_code=WebAuthCode、
                                                                                                                                    grant_type=authorization_code
    微信网页         https://api.weixin.qq.com/sns/oauth2/access_token      appid、
                                                                                                                                     secret、
                                                                                                                                     code=WebAuthCode、
                                                                                                                                     grant_type=authorization_code
    注意:
        1.这里得到的返回结果:除了openid,还有access_token
        2.除了小程序外,其它应用开发(如:公众号、web应用、商户)获取openid的url地址是一 样,只是appid和secret参数不同,如果是公众号获取openid,则参数填写公众号的openid和secret,同理商户和其它应用,appid和secret不同也就决定了在这个应用下的用户对应的openid是不同的。
    东湖网上支付,采用了两种支付方式:H5支付和公众号支付,都是采用“微信网页”的同一URL来获取webauthcode来交换openid。这里面涉及到3个openid(用户关注的公众号openid,微信网页移动应用openid,商户openid),那不管是哪种支付,获取的Openid都是由商户的appid和secret参数来获得这个用户在商户应用中的openid。问题来了,参见下面的"问题二"。
问题一:如果我们项目中有了个UserId,要能关联同一微信平台下的全部应用(移动应用、网站应用、公众帐号、小程序),怎么关联呢?
问题二:在公众号内移动应用进行H5支付,怎么确定是同一个用户呢?
open.weixin.qq.com的unionid机制。开放平台和UnionId机制参考说明
使用unionid机制前提open.weixin.qq.com中注册为开发者认证绑定开发者账号下的所有应用
如果不需要确定区分每种应用中的用户是不是同一用户,则不用理会unionid机制。 
小程序中获取openId方法:
    1.调用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等信息
        方式一:直接在小程序中调用(问题:https://api.weixin.qq.com 不在以下 request 合法域名列表中)
            在调试阶段,我们可以在微信开发者工具中禁用这个警告,考虑到小程序的发布版本,
            小程序中我们要设置自己的后台合法域名,又不能同时设置两个request合法域名            

第十一节:OpenId、UnionId、Session_key_第1张图片
小程序后台设置后台合法域名

        方式二:通过我们自己的后台去微信平台URL获取
            小程序获取到web authorize code传入到我们自己的后台去获取openId
            官方:
                1.小程序调用wx.login,wx.getUserInfo获取临时登录凭证code,并回传到开发者服务器
                2.开发者服务器以code换取用户唯一标识openid会话密钥session_key
                之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。具体实现流程如下:     

第十一节:OpenId、UnionId、Session_key_第2张图片
安全使用openid和session_key

    官方提醒
        //正常返回的JSON数据包
        {"openid":"OPENID","session_key":"SESSIONKEY",}
        //满足UnionID返回条件时,返回的JSON数据包 
        //满足前提open.weixin.qq.com中注册为开发者认证绑定开发者账号下的所有应用      
        {"openid":"OPENID","session_key":"SESSIONKEY","unionid":"UNIONID"}
        //错误时返回JSON数据包(示例为Code无效)
        {"errcode":40029,"errmsg":"invalid code"}    
    3.获取加密信息
    如果只是拿小程序的openid和unionid,则不用考虑这步,
    因为上面第2步就可拿到openid和unionid(满足unionid机制)

    在调用 wx.login,wx.getUserInfo都会返回一个对象,这个对象有一个属性叫encryptedData            

第十一节:OpenId、UnionId、Session_key_第3张图片
得到的encryptedData

这个字段的数据是加密的,所以我们可以通过解密的方式来得到原文信息,解密后为json,结构如下:
       {"openId":"OPENID","nickName":"NICKNAME","gender": GENDER,"city":"CITY","province":"PROVINCE","country":"COUNTRY","avatarUrl":"AVATARURL","unionId":"UNIONID","watermark": {"appid":"APPID","timestamp":TIMESTAMP }}
  新的数据解密方法 
        总的来说还是原来的算法,还是原来的逻辑结构,不同的是解密方式,
        以前通过session_key得到iv,新方法是直接从前台接口处得到iv来解密,所改变的也只是传输的数据
        1.对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
        2.对称解密的目标密文为 Base64_Decode(encryptedData)。
        3.对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
        4.对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
详细流程:
1.小程序客户端调用wx.login,回调里面包含js_code。
2.然后将js_code发送到服务器A(开发者服务器),服务器A向微信服务器发起请求附带js_code、appId、secretkey和grant_type参数,以换取用户的openid和session_key(会话密钥)。
3.服务器A拿到session_key后,生成一个随机数我们叫3rd_session,以3rdSessionId为key,以session_key + openid为value缓存到redis或memcached中;因为微信团队不建议直接将session_key在网络上传输,由开发者自行生成唯一键与session_key关联。其作用是:
    将3rdSessionId返回给客户端,维护小程序登录态。
    通过3rdSessionId找到用户session_key和openid。
4.客户端拿到3rdSessionId后缓存到storage,
5.通过wx.getUserIinfo可以获取到用户敏感数据encryptedData 。
6.客户端将encryptedData、3rdSessionId和偏移量一起发送到服务器A
7.服务器A根据3rdSessionId从缓存中获取session_key
8.在服务器A使用AES解密encryptedData,从而实现用户敏感数据解密
重点在6、7、8三个环节。
服务器端解密代码示例
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;

    }


   /**

    *解密用户敏感数据获取用户信息

    * @param sessionKey数据进行加密签名的密钥

    * @param encryptedData包括敏感数据在内的完整用户信息的加密数据

    * @param iv加密算法的初始向量

    * @return

    */

   public JSONObject getUserInfo(String encryptedData,StringsessionKey,String iv){

       //被加密的数据

       byte[] dataByte = Base64.decode(encryptedData);

       //加密秘钥

       byte[] keyByte = Base64.decode(sessionKey);

       //偏移量

       byte[] ivByte = Base64.decode(iv);


       try {

               //如果密钥不足16位,那么就补足.  这个if 中的内容很重要

           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;

    }


}

你可能感兴趣的:(第十一节:OpenId、UnionId、Session_key)