zoukankan      html  css  js  c++  java
  • 微信公众号&小程序 -- 获取并解密用户数据(获取openId、unionId)

    本文转自https://my.oschina.net/u/3235888/blog/832895

    前言

    • 微信小程序API文档:https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html
    • 在实际的小程序开发中,往往需要用户授权登陆并获取用户的数据,快速对接用户系统。
    • openId : 用户在当前小程序的唯一标识 
    • unionId : 如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionId是相同的。详情登录微信开放平台(open.weixin.qq.com) 。
    • 在微信小程序开发中,unionId等敏感数据则被加密在encryptedData,于是需要以下流程来解密敏感数据,从而获取unionId等信息。

    流程

    1、(客户端)微信小程序客户端调用 wx.login()接口获取登录凭证(code)

    //1、调用微信登录接口,获取code
    wx.login({
        success: function (r) {
            var code = r.code;//登录凭证
            if (code) {
                //2、调用获取用户信息接口
                //...
    
            } else {
                console.log('获取用户登录态失败!' + r.errMsg)
            }
        },
        fail: function () {
            callback(false)
        }
    })

    2、(客户端)微信小程序客户端调用 wx.getUserInfo()接口获取 用户基本信息、encryptedData(用户敏感信息加密数据) 和 iv(加密算法的初始向量 )

    //1、调用微信登录接口,获取code
    wx.login({
        success: function (r) {
            var code = r.code;//登录凭证
            if (code) {
                //2、调用获取用户信息接口
                wx.getUserInfo({
                    success: function (res) {
                        console.log({encryptedData: res.encryptedData, iv: res.iv, code: code})
                        //3.解密用户信息 获取unionId
                        //...
                    },
                    fail: function () {
                        console.log('获取用户信息失败')
                    }
                })
    
            } else {
                console.log('获取用户登录态失败!' + r.errMsg)
            }
        },
        fail: function () {
            callback(false)
        }
    })

    3、(客户端)将前面获取到的 code 、encryptedData、iv发送到自己的服务器(开发者服务器),通过自己的服务器(开发者服务器)解密获取信息

    //1、调用微信登录接口,获取code
    wx.login({
        success: function (r) {
            var code = r.code;//登录凭证
            if (code) {
                //2、调用获取用户信息接口
                wx.getUserInfo({
                    success: function (res) {
                        console.log({encryptedData: res.encryptedData, iv: res.iv, code: code})
                        //3.请求自己的服务器,解密用户信息 获取unionId等加密信息
                        wx.request({
                            url: 'https://xxxx.com/wxsp/decodeUserInfo',//自己的服务接口地址
                            method: 'post',
                            header: {
                                'content-type': 'application/x-www-form-urlencoded'
                            },
                            data: {encryptedData: res.encryptedData, iv: res.iv, code: code},
                            success: function (data) {
    
                                //4.解密成功后 获取自己服务器返回的结果
                                if (data.data.status == 1) {
                                    var userInfo_ = data.data.userInfo;
                                    console.log(userInfo_)
                                } else {
                                    console.log('解密失败')
                                }
    
                            },
                            fail: function () {
                                console.log('系统错误')
                            }
                        })
                    },
                    fail: function () {
                        console.log('获取用户信息失败')
                    }
                })
    
            } else {
                console.log('获取用户登录态失败!' + r.errMsg)
            }
        },
        fail: function () {
             console.log('登陆失败')
        }
    })

    4、(服务端 java)自己的服务器发送code到微信服务器获取openid(用户唯一标识)和session_key(会话密钥),最后将encryptedData、iv、session_key通过AES解密获取到用户敏感数据

        a、获取秘钥并处理解密的controller(这里用的是springMVC)

    /**
         * 解密用户敏感数据
         *
         * @param encryptedData 明文,加密数据
         * @param iv            加密算法的初始向量
         * @param code          用户允许登录后,回调内容会带上 code(有效期五分钟),开发者需要将 code 发送到开发者服务器后台,使用code 换取 session_key api,将 code 换成 openid 和 session_key
         * @return
         */
        @ResponseBody
        @RequestMapping(value = "/decodeUserInfo", method = RequestMethod.POST)
        public Map decodeUserInfo(String encryptedData, String iv, String code) {
    
            Map map = new HashMap();
    
            //登录凭证不能为空
            if (code == null || code.length() == 0) {
                map.put("status", 0);
                map.put("msg", "code 不能为空");
                return map;
            }
    
            //小程序唯一标识   (在微信小程序管理后台获取)
            String wxspAppid = "xxxxxxxxxxxxxx";
            //小程序的 app secret (在微信小程序管理后台获取)
            String wxspSecret = "xxxxxxxxxxxxxx";
            //授权(必填)
            String grant_type = "authorization_code";
    
    
            //////////////// 1、向微信服务器 使用登录凭证 code 获取 session_key 和 openid ////////////////
            //请求参数
            String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + code + "&grant_type=" + grant_type;
            //发送请求
            String sr = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
            //解析相应内容(转换成json对象)
            JSONObject json = JSONObject.fromObject(sr);
            //获取会话密钥(session_key)
            String session_key = json.get("session_key").toString();
            //用户的唯一标识(openid)
            String openid = (String) json.get("openid");
    
            //////////////// 2、对encryptedData加密数据进行AES解密 ////////////////
            try {
                String result = AesCbcUtil.decrypt(encryptedData, session_key, iv, "UTF-8");
                if (null != result && result.length() > 0) {
                    map.put("status", 1);
                    map.put("msg", "解密成功");
    
                    JSONObject userInfoJSON = JSONObject.fromObject(result);
                    Map userInfo = new HashMap();
                    userInfo.put("openId", userInfoJSON.get("openId"));
                    userInfo.put("nickName", userInfoJSON.get("nickName"));
                    userInfo.put("gender", userInfoJSON.get("gender"));
                    userInfo.put("city", userInfoJSON.get("city"));
                    userInfo.put("province", userInfoJSON.get("province"));
                    userInfo.put("country", userInfoJSON.get("country"));
                    userInfo.put("avatarUrl", userInfoJSON.get("avatarUrl"));
                    userInfo.put("unionId", userInfoJSON.get("unionId"));
                    map.put("userInfo", userInfo);
                    return map;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            map.put("status", 0);
            map.put("msg", "解密失败");
            return map;
        }

        b、AesCbcUtil.java 工具类

    package com.yfs.util;
    
    import org.apache.commons.codec.binary.Base64;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.UnsupportedEncodingException;
    import java.security.*;
    import java.security.spec.InvalidParameterSpecException;
    
    /**
     * Created by yfs on 2017/2/6.
     * <p>
     * AES-128-CBC 加密方式
     * 注:
     * AES-128-CBC可以自己定义“密钥”和“偏移量“。
     * AES-128是jdk自动生成的“密钥”。
     */
    public class AesCbcUtil {
    
    
        static {
            //BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/
            Security.addProvider(new BouncyCastleProvider());
        }
    
        /**
         * AES解密
         *
         * @param data           //密文,被加密的数据
         * @param key            //秘钥
         * @param iv             //偏移量
         * @param encodingFormat //解密后的结果需要进行的编码
         * @return
         * @throws Exception
         */
        public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception {
    //        initialize();
    
            //被加密的数据
            byte[] dataByte = Base64.decodeBase64(data);
            //加密秘钥
            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;
        }
    
    }
    

        c、HttpRequest.java 工具类

    package com.yfs.util;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.URL;
    import java.net.URLConnection;
    import java.util.List;
    import java.util.Map;
    
    public class HttpRequest {
    
        public static void main(String[] args) {
            //发送 GET 请求
            String s=HttpRequest.sendGet("http://v.qq.com/x/cover/kvehb7okfxqstmc.html?vid=e01957zem6o", "");
            System.out.println(s);
    
    //        //发送 POST 请求
    //        String sr=HttpRequest.sendPost("http://www.toutiao.com/stream/widget/local_weather/data/?city=%E4%B8%8A%E6%B5%B7", "");
    //        JSONObject json = JSONObject.fromObject(sr);
    //        System.out.println(json.get("data"));
        }
    
        /**
         * 向指定URL发送GET方法的请求
         * 
         * @param url
         *            发送请求的URL
         * @param param
         *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
         * @return URL 所代表远程资源的响应结果
         */
        public static String sendGet(String url, String param) {
            String result = "";
            BufferedReader in = null;
            try {
                String urlNameString = url + "?" + param;
                URL realUrl = new URL(urlNameString);
                // 打开和URL之间的连接
                URLConnection connection = realUrl.openConnection();
                // 设置通用的请求属性
                connection.setRequestProperty("accept", "*/*");
                connection.setRequestProperty("connection", "Keep-Alive");
                connection.setRequestProperty("user-agent",
                        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
                // 建立实际的连接
                connection.connect();
                // 获取所有响应头字段
                Map<String, List<String>> map = connection.getHeaderFields();
                // 遍历所有的响应头字段
                for (String key : map.keySet()) {
                    System.out.println(key + "--->" + map.get(key));
                }
                // 定义 BufferedReader输入流来读取URL的响应
                in = new BufferedReader(new InputStreamReader(
                        connection.getInputStream()));
                String line;
                while ((line = in.readLine()) != null) {
                    result += line;
                }
            } catch (Exception e) {
                System.out.println("发送GET请求出现异常!" + e);
                e.printStackTrace();
            }
            // 使用finally块来关闭输入流
            finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            return result;
        }
    
        /**
         * 向指定 URL 发送POST方法的请求
         * 
         * @param url
         *            发送请求的 URL
         * @param param
         *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
         * @return 所代表远程资源的响应结果
         */
        public static String sendPost(String url, String param) {
            PrintWriter out = null;
            BufferedReader in = null;
            String result = "";
            try {
                URL realUrl = new URL(url);
                // 打开和URL之间的连接
                URLConnection conn = realUrl.openConnection();
                // 设置通用的请求属性
                conn.setRequestProperty("accept", "*/*");
                conn.setRequestProperty("connection", "Keep-Alive");
                conn.setRequestProperty("user-agent",
                        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
                // 发送POST请求必须设置如下两行
                conn.setDoOutput(true);
                conn.setDoInput(true);
                // 获取URLConnection对象对应的输出流
                out = new PrintWriter(conn.getOutputStream());
                // 发送请求参数
                out.print(param);
                // flush输出流的缓冲
                out.flush();
                // 定义BufferedReader输入流来读取URL的响应
                in = new BufferedReader(
                        new InputStreamReader(conn.getInputStream()));
                String line;
                while ((line = in.readLine()) != null) {
                    result += line;
                }
            } catch (Exception e) {
                System.out.println("发送 POST 请求出现异常!"+e);
                e.printStackTrace();
            }
            //使用finally块来关闭输出流、输入流
            finally{
                try{
                    if(out!=null){
                        out.close();
                    }
                    if(in!=null){
                        in.close();
                    }
                }
                catch(IOException ex){
                    ex.printStackTrace();
                }
            }
            return result;
        }    
    }

    备注

    1. 代码中需要用到的jar包,请自行下载并添加到项目中。
    2. 这里需要注意的是,AES解密的时候需要用到javax.crypto.*包的类,在jdk的 jce.jar中提供,是jdk自带的库。如果是MAVEN项目,则需要在pom.xml文件中配置指定编译路径jce.jar

        在bootclasspath中添加jce.jar的地址 :<bootclasspath>${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/jre/lib/jce.jar</bootclasspath> ,注意rt.jar 和 jce.jar 间以:号连接

        如:

    <build>
      <finalName>com.yfs.app</finalName>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.5.1</version>
          <configuration>
            <source>1.7</source>
            <target>1.7</target>
            <compilerArguments>
              <verbose />
              <bootclasspath>${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/jre/lib/jce.jar</bootclasspath>
            </compilerArguments>
            <encoding>utf-8</encoding>
          </configuration>
        </plugin>
    
      </plugins>
    </build>

    总结

        好了,总算完成数据解密了,接下来对接新的或已有的用户系统都妥妥的。

        有一点需要注意的是,要对接已有的用户系统可能需要用到unionId,如果通过以上方法获取不到unionId,那么你就要去检查一下你的微信开放平台(https://open.weixin.qq.com)是否有绑定微信小程序咯~

    微信公众号网页登录的思路和流程:

    第一步:获取到code凭证,
    第二步:拿到code凭证去获取用户openid和access_token,refresh_token
    第三步:刷新access_token,也是那openid和refresh_token
    第四步:就是拿openid和access_token去获取用户信息
    第五步:就是拿openid和access_token去获取用户信息,看能不能获取到,获取不到在调用刷新access_token的接口
    在去调用第四步。

        

        前段时间刚做完微信小程序的项目,之间遇到的一些问题会陆续发布,欢迎大家一起来探讨。

  • 相关阅读:
    JAVA-初步认识-第十三章-验证静态同步函数的锁
    JAVA-初步认识-第十三章-多线程(验证同步函数的锁)
    JAVA-初步认识-第十二章-面向对象(包与包之间的访问)
    JAVA-初步认识-第十二章-面向对象(包的概述)
    JAVA-初步认识-第十三章-同步函数
    Fatal error: Call to undefined function imagettftext()解决办法
    ecstore菜鸟电子面单对接摘要
    linux crontab 实现每秒执行(转)
    ios9 URL Schemes列为白名单,才可正常检查其他应用是否安装
    主机宝等主机面板不能跨站访问文件,不能访问父路径文件问题
  • 原文地址:https://www.cnblogs.com/panchanggui/p/11314757.html
Copyright © 2011-2022 走看看