第十一节: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合法域名            

小程序后台设置后台合法域名

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

安全使用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            

得到的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;

    }


}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容