一、功能描述
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 }