Wechat Public Number & Small Program - Obtain and Decrypt User Data (Obtain openId, unionId)

This article is from https://my.oschina.net/u/3235888/blog/832895.

 

Preface

  • Wechat applet API document: https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html
  • In the actual development of small programs, users are often required to authorize login and obtain user data to quickly dock with user systems.
  • openId: User's unique identity in the current applet
  • UnionId: If a developer has multiple mobile applications, website applications, and public accounts (including widgets), unionid can be used to distinguish the uniqueness of users, because as long as they are mobile applications, website applications and public accounts (including widgets) under the same Weichat Open Platform account, the unionid of users is unique. . In other words, unionId is the same for different applications on the same open platform. Details are logged on to Weixin Open Platform (open.weixin.qq.com).
  • In the development of Wechat widget program, sensitive data such as unionId are encrypted data, so the following process is needed to decrypt sensitive data to obtain unionId and other information.

 

Technological process

1. (Client) Microsoft widget client calls wx.login() interface to obtain login credentials (code)

//1. Call the login interface of Wechat to get the code
wx.login({
    success: function (r) {
        var code = r.code;//Logon Credentials
        if (code) {
            //2. Call to get user information interface
            //...

        } else {
            console.log('Failed to get user login status!' + r.errMsg)
        }
    },
    fail: function () {
        callback(false)
    }
})

2. (Client) The client of Wechat applet calls wx.getUserInfo() interface to obtain user basic information, encrypted data (user sensitive information encrypted data) and iv (initial vector of encryption algorithm)

//1. Call the login interface of Wechat to get the code
wx.login({
    success: function (r) {
        var code = r.code;//Logon Credentials
        if (code) {
            //2. Call to get user information interface
            wx.getUserInfo({
                success: function (res) {
                    console.log({encryptedData: res.encryptedData, iv: res.iv, code: code})
                    //3. Decrypting User Information to Acquire unionId
                    //...
                },
                fail: function () {
                    console.log('Failed to obtain user information')
                }
            })

        } else {
            console.log('Failed to get user login status!' + r.errMsg)
        }
    },
    fail: function () {
        callback(false)
    }
})

3. (Client) Send the code, encrypted data and iv obtained above to its own server (developer server), and decrypt the information through its own server (developer server).

//1. Call the login interface of Wechat to get the code
wx.login({
    success: function (r) {
        var code = r.code;//Logon Credentials
        if (code) {
            //2. Call to get user information interface
            wx.getUserInfo({
                success: function (res) {
                    console.log({encryptedData: res.encryptedData, iv: res.iv, code: code})
                    //3. Request your own server to decrypt user information and obtain encrypted information such as unionId
                    wx.request({
                        url: 'https://xxxx.com/wxsp/decodeUserInfo',//My own service interface address
                        method: 'post',
                        header: {
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: {encryptedData: res.encryptedData, iv: res.iv, code: code},
                        success: function (data) {

                            //4. After successful decryption, get the results returned by your server
                            if (data.data.status == 1) {
                                var userInfo_ = data.data.userInfo;
                                console.log(userInfo_)
                            } else {
                                console.log('Decryption failed')
                            }

                        },
                        fail: function () {
                            console.log('System error')
                        }
                    })
                },
                fail: function () {
                    console.log('Failed to obtain user information')
                }
            })

        } else {
            console.log('Failed to get user login status!' + r.errMsg)
        }
    },
    fail: function () {
         console.log('Landing Failure')
    }
})

4. (Server-side java) The server sends code to the Wechat server to get the openid (user's unique identity) and session_key (session key). Finally, encrypted data, iv, session_key are decrypted by AES to get user-sensitive data.

a. controller that obtains the secret key and processes decryption (springMVC is used here)

/**
     * Decrypting User Sensitive Data
     *
     * @param encryptedData Plaintext, encrypted data
     * @param iv            Initial Vector of Encryption Algorithms
     * @param code          After the user is allowed to log in, the callback content will take the code (valid for five minutes). The developer needs to send the code to the developer server background, exchange the code for session_key api, and replace the code with openid and session_key.
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/decodeUserInfo", method = RequestMethod.POST)
    public Map decodeUserInfo(String encryptedData, String iv, String code) {

        Map map = new HashMap();

        //Logon credentials cannot be empty
        if (code == null || code.length() == 0) {
            map.put("status", 0);
            map.put("msg", "code Can't be empty");
            return map;
        }

        //Unique ID of widget (obtained in the background of Wechat widget management)
        String wxspAppid = "xxxxxxxxxxxxxx";
        //app secret of widget (acquired in the background of Wechat widget management)
        String wxspSecret = "xxxxxxxxxxxxxx";
        //Authorization (required)
        String grant_type = "authorization_code";


        //////////////// 1. Obtain session_key and openid from Wechat server using login credential code////////////////
        //Request parameters
        String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + code + "&grant_type=" + grant_type;
        //Send requests
        String sr = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
        //Parsing the corresponding content (converting to json objects)
        JSONObject json = JSONObject.fromObject(sr);
        //Get session_key
        String session_key = json.get("session_key").toString();
        //User's Unique Identity (openid)
        String openid = (String) json.get("openid");

        //////////////// 2. AES Decryption of Encrypted Data Encrypted Data////////////////
        try {
            String result = AesCbcUtil.decrypt(encryptedData, session_key, iv, "UTF-8");
            if (null != result && result.length() > 0) {
                map.put("status", 1);
                map.put("msg", "Successful decryption");

                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", "Decryption failed");
        return map;
    }

b, AesCbcUtil.java tool class

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 Encryption mode
 * Note:
 * AES-128-CBC You can define "key" and "offset" yourself.
 * AES-128 It is the "key" automatically generated by jdk.
 */
public class AesCbcUtil {


    static {
        //BouncyCastle is an open source encryption and decryption solution with its home page at http://www.bouncycastle.org./
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * AES Decrypt
     *
     * @param data           //Ciphertext, encrypted data
     * @param key            //Secret key
     * @param iv             //Offset
     * @param encodingFormat //Encoding of decrypted results
     * @return
     * @throws Exception
     */
    public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception {
//        initialize();

        //Encrypted data
        byte[] dataByte = Base64.decodeBase64(data);
        //Encryption key
        byte[] keyByte = Base64.decodeBase64(key);
        //Offset
        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);// Initialization

            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 tool class

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) {
        //Send GET requests
        String s=HttpRequest.sendGet("http://v.qq.com/x/cover/kvehb7okfxqstmc.html?vid=e01957zem6o", "");
        System.out.println(s);

//        // Send POST requests
//        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"));
    }

    /**
     * Send a GET method request to the specified URL
     * 
     * @param url
     *            The URL to send the request
     * @param param
     *            The request parameter should be in the form of name1 = value1 & Name2 = value2.
     * @return URL Response results of the remote resources represented
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // Open the connection between the URL
            URLConnection connection = realUrl.openConnection();
            // Setting Common Request Properties
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // Establish actual connections
            connection.connect();
            // Get all response header fields
            Map<String, List<String>> map = connection.getHeaderFields();
            // Traverse through all response header fields
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // Define the BufferedReader input stream to read the response of the URL
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("An exception occurred when sending GET request! "+e";
            e.printStackTrace();
        }
        // Close the input stream using the final block
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * Send a request for a POST method to a specified URL
     * 
     * @param url
     *            The URL to send the request
     * @param param
     *            The request parameter should be in the form of name1 = value1 & Name2 = value2.
     * @return Response results of the remote resources represented
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // Open the connection between the URL
            URLConnection conn = realUrl.openConnection();
            // Setting Common Request Properties
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // To send a POST request, you must set the following two lines
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // Get the output stream corresponding to the URLConnection object
            out = new PrintWriter(conn.getOutputStream());
            // Send request parameters
            out.print(param);
            // Buffer of flush output stream
            out.flush();
            // Define the BufferedReader input stream to read the response of the URL
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("Send out POST The request is abnormal!"+e);
            e.printStackTrace();
        }
        //Close the output and input streams using the final block
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }    
}

 

Remarks

  1. The jar package you need in your code should be downloaded and added to your project.
  2. It is important to note that the classes of the javax.crypto. * package are used in AES decryption. They are provided in jce. jar of jdk. They are the libraries of jdk. If you are a MAVEN project, you need to configure the specified compilation path jce.jar in the pom.xml file

Add the address of jce.jar in bootclasspath: <bootclasspath>${JAVA_HOME}/ jre/lib/rt.jar: ${JAVA_HOME}/ jre/lib/jce.jar</bootclasspath>, note that the connection between rt.jar and jce.jar is numbered:

For example:

<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>

 

summary

All right, the data has been decrypted at last, and it's all right to dock with new or existing user systems.

One thing to note is that unionId may be needed to connect with existing user systems. If unionId is not available through the above methods, you should check whether your open platform (https://open.weixin.qqq.com) has a widget-bound widget.~

    

Some time ago, we just finished the project of Wechat applet, and some problems will be released one after another. Welcome to discuss them together.

Keywords: Python Java JSON JDK encoding

Added by priya_cks on Wed, 07 Aug 2019 08:44:17 +0300