一、功能描述
openId是某个微信账户对应某个小程序或者公众号的唯一标识,但openId必须经过后台解密才能获取(之前实现过前台解密,可是由于微信小程序的种种限制,前台解密无法在小程序发布后使用)
二、实现流程
1. 获取微信用户的登录信息;
2. 将encryptedData中的数据作为参数传给java后台
3. java后台进行解密
三、代码实现
1. 后台的解密代码
1 /**
2 * decoding encrypted data to get openid
3 *
4 * @param iv
5 * @param encryptedData
6 * @param code
7 * @return
8 */
9 @RequestMapping(value = "/decodeUserInfo", method = RequestMethod.GET)
10 private Map decodeUserInfo(String iv, String encryptedData, String code) {
11 Map map = new HashMap();
12 // login code can not be null
13 if (code == null || code.length() == 0) {
14 map.put("status", 0);
15 map.put("msg", "code 不能为空");
16 return map;
17 }
18 // mini-Program's AppID
19 String wechatAppId = "你的小程序的AppID";
20
21 // mini-Program's session-key
22 String wechatSecretKey = "你的小程序的session-key";
23
24 String grantType = "authorization_code";
25
26 // using login code to get sessionId and openId
27 String params = "appid=" + wechatAppId + "&secret=" + wechatSecretKey + "&js_code=" + code + "&grant_type=" + grantType;
28
29 // sending request
30 String sr = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
31
32 // analysis request content
33 JSONObject json = JSONObject.fromObject(sr);
34
35 // getting session_key
36 String sessionKey = json.get("session_key").toString();
37
38 // getting open_id
39 String openId = json.get("openid").toString();
40
41 // decoding encrypted info with AES
42 try {
43 String result = AesCbcUtil.decrypt(encryptedData, sessionKey, iv, "UTF-8");
44 if (null != result && result.length() > 0) {
45 map.put("status", 1);
46 map.put("msg", "解密成功");
47
48 JSONObject userInfoJSON = JSONObject.fromObject(result);
49 Map userInfo = new HashMap();
50 userInfo.put("openId", userInfoJSON.get("openId"));
51 userInfo.put("nickName", userInfoJSON.get("nickName"));
52 userInfo.put("gender", userInfoJSON.get("gender"));
53 userInfo.put("city", userInfoJSON.get("city"));
54 userInfo.put("province", userInfoJSON.get("province"));
55 userInfo.put("country", userInfoJSON.get("country"));
56 userInfo.put("avatarUrl", userInfoJSON.get("avatarUrl"));
57 userInfo.put("unionId", userInfoJSON.get("unionId"));
58 map.put("userInfo", userInfo);
59 return map;
60 }
61
62
63 } catch (Exception e) {
64 e.printStackTrace();
65 }
66 map.put("status", 0);
67 map.put("msg", "解密失败");
68 return map;
69 }
2. 前台代码
1 wx.login({
2 success: function (res) {
3 that.globalData.code = res.code;//登录凭证
4 if (that.globalData.code) {
5 //2、调用获取用户信息接口
6 // 查看是否授权
7 wx.getUserInfo({
8 success: function (res) {
9 that.globalData.encryptedData = res.encryptedData
10 that.globalData.iv = res.iv
11 console.log('[INFO] app.js/ ',{ encryptedData: res.encryptedData, iv: res.iv, code: that.globalData.code })
12 //3.请求自己的服务器,解密用户信息 获取unionId等加密信息
13 wx.request({
14 url: 'https://www.****.cn/***/****/decodeUserInfo',//自己的服务接口地址
15 method: 'get',
16 header: {
17 "Content-Type": "applciation/json"
18 },
19 data: { encryptedData: res.encryptedData, iv: res.iv, code: that.globalData.code },
20 success: function (data) {
21
22 //4.解密成功后 获取自己服务器返回的结果
23 if (data.data.status == 1) {
24 var userInfos = data.data.userInfo;
25 that.globalData.openId = userInfos.openId;
26 console.log('[INFO] app.js/ userInfo:',userInfos)
27 } else {
28 console.log('[INFO] app.js/ 解密失败')
29 }
30 },
31 fail: function () {
32 console.log('[INFO] app.js/ 系统错误')
33 }
34 })
35 },
36 fail: function () {
37 console.log('[INFO] app.js/ 获取用户信息失败')
38 }
39 })
40 } else {
41 console.log('[INFO] app.js/ 获取用户登录态失败!' + r.errMsg)
42 }
43 },
44 fail: function () {
45 console.log('[INFO] app.js/ 登陆失败')
46 }
47 })
48
49 // 获取用户信息
50 wx.getSetting({
51 success: res => {
52 if (res.authSetting['scope.userInfo']) {
53 // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
54 wx.getUserInfo({
55 success: res => {
56 // 可以将 res 发送给后台解码出 unionId
57 this.globalData.userInfo = res.userInfo;
58
59 // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
60 // 所以此处加入 callback 以防止这种情况
61 if (this.userInfoReadyCallback) {
62 this.userInfoReadyCallback(res)
63 }
64 }
65 })
66 }
67 }
68 })
69 },
2. HttpRequest工具类
1 import java.io.BufferedReader;
2 import java.io.IOException;
3 import java.io.InputStreamReader;
4 import java.io.PrintWriter;
5 import java.net.URL;
6 import java.net.URLConnection;
7 import java.util.List;
8 import java.util.Map;
9
10 public class HttpRequest {
11
12 public static void main(String[] args) {
13 //发送 GET 请求
14 String s=HttpRequest.sendGet("http://v.qq.com/x/cover/kvehb7okfxqstmc.html?vid=e01957zem6o", "");
15 System.out.println(s);
16
17 // //发送 POST 请求
18 // String sr=HttpRequest.sendPost("http://www.toutiao.com/stream/widget/local_weather/data/?city=%E4%B8%8A%E6%B5%B7", "");
19 // JSONObject json = JSONObject.fromObject(sr);
20 // System.out.println(json.get("data"));
21 }
22
23 /**
24 * 向指定URL发送GET方法的请求
25 *
26 * @param url
27 * 发送请求的URL
28 * @param param
29 * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
30 * @return URL 所代表远程资源的响应结果
31 */
32 public static String sendGet(String url, String param) {
33 String result = "";
34 BufferedReader in = null;
35 try {
36 String urlNameString = url + "?" + param;
37 URL realUrl = new URL(urlNameString);
38 // 打开和URL之间的连接
39 URLConnection connection = realUrl.openConnection();
40 // 设置通用的请求属性
41 connection.setRequestProperty("accept", "*/*");
42 connection.setRequestProperty("connection", "Keep-Alive");
43 connection.setRequestProperty("user-agent",
44 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
45 // 建立实际的连接
46 connection.connect();
47 // 获取所有响应头字段
48 Map<String, List<String>> map = connection.getHeaderFields();
49 // 遍历所有的响应头字段
50 for (String key : map.keySet()) {
51 System.out.println(key + "--->" + map.get(key));
52 }
53 // 定义 BufferedReader输入流来读取URL的响应
54 in = new BufferedReader(new InputStreamReader(
55 connection.getInputStream()));
56 String line;
57 while ((line = in.readLine()) != null) {
58 result += line;
59 }
60 } catch (Exception e) {
61 System.out.println("发送GET请求出现异常!" + e);
62 e.printStackTrace();
63 }
64 // 使用finally块来关闭输入流
65 finally {
66 try {
67 if (in != null) {
68 in.close();
69 }
70 } catch (Exception e2) {
71 e2.printStackTrace();
72 }
73 }
74 return result;
75 }
76
77 /**
78 * 向指定 URL 发送POST方法的请求
79 *
80 * @param url
81 * 发送请求的 URL
82 * @param param
83 * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
84 * @return 所代表远程资源的响应结果
85 */
86 public static String sendPost(String url, String param) {
87 PrintWriter out = null;
88 BufferedReader in = null;
89 String result = "";
90 try {
91 URL realUrl = new URL(url);
92 // 打开和URL之间的连接
93 URLConnection conn = realUrl.openConnection();
94 // 设置通用的请求属性
95 conn.setRequestProperty("accept", "*/*");
96 conn.setRequestProperty("connection", "Keep-Alive");
97 conn.setRequestProperty("user-agent",
98 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
99 // 发送POST请求必须设置如下两行
100 conn.setDoOutput(true);
101 conn.setDoInput(true);
102 // 获取URLConnection对象对应的输出流
103 out = new PrintWriter(conn.getOutputStream());
104 // 发送请求参数
105 out.print(param);
106 // flush输出流的缓冲
107 out.flush();
108 // 定义BufferedReader输入流来读取URL的响应
109 in = new BufferedReader(
110 new InputStreamReader(conn.getInputStream()));
111 String line;
112 while ((line = in.readLine()) != null) {
113 result += line;
114 }
115 } catch (Exception e) {
116 System.out.println("发送 POST 请求出现异常!"+e);
117 e.printStackTrace();
118 }
119 //使用finally块来关闭输出流、输入流
120 finally{
121 try{
122 if(out!=null){
123 out.close();
124 }
125 if(in!=null){
126 in.close();
127 }
128 }
129 catch(IOException ex){
130 ex.printStackTrace();
131 }
132 }
133 return result;
134 }
135 }
3. AesCbuUtil工具类
1 import org.apache.commons.codec.binary.Base64;
2 import org.bouncycastle.jce.provider.BouncyCastleProvider;
3
4 import javax.crypto.BadPaddingException;
5 import javax.crypto.Cipher;
6 import javax.crypto.IllegalBlockSizeException;
7 import javax.crypto.NoSuchPaddingException;
8 import javax.crypto.spec.IvParameterSpec;
9 import javax.crypto.spec.SecretKeySpec;
10 import java.io.UnsupportedEncodingException;
11 import java.security.*;
12 import java.security.spec.InvalidParameterSpecException;
13
14 public class AesCbcUtil {
15
16 static {
17 //BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/
18 Security.addProvider(new BouncyCastleProvider());
19 }
20
21 /**
22 * AES解密
23 *
24 * @param data //密文,被加密的数据
25 * @param key //秘钥
26 * @param iv //偏移量
27 * @param encodingFormat //解密后的结果需要进行的编码
28 * @return
29 * @throws Exception
30 */
31 public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception {
32 // initialize();
33
34 //被加密的数据
35 byte[] dataByte = Base64.decodeBase64(data.getBytes());
36 //加密秘钥
37 byte[] keyByte = Base64.decodeBase64(key.getBytes());
38 //偏移量
39 byte[] ivByte = Base64.decodeBase64(iv.getBytes());
40
41
42 try {
43 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
44
45 SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
46
47 AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
48 parameters.init(new IvParameterSpec(ivByte));
49
50 cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
51
52 byte[] resultByte = cipher.doFinal(dataByte);
53 if (null != resultByte && resultByte.length > 0) {
54 String result = new String(resultByte, encodingFormat);
55 return result;
56 }
57 return null;
58 } catch (NoSuchAlgorithmException e) {
59 e.printStackTrace();
60 } catch (NoSuchPaddingException e) {
61 e.printStackTrace();
62 } catch (InvalidParameterSpecException e) {
63 e.printStackTrace();
64 } catch (InvalidKeyException e) {
65 e.printStackTrace();
66 } catch (InvalidAlgorithmParameterException e) {
67 e.printStackTrace();
68 } catch (IllegalBlockSizeException e) {
69 e.printStackTrace();
70 } catch (BadPaddingException e) {
71 e.printStackTrace();
72 } catch (UnsupportedEncodingException e) {
73 e.printStackTrace();
74 }
75
76 return null;
77 }
78
79 }