最近研究小程序 , 客户必须要unionid来对微信用户做免登陆的处理.
微信方还没提供解密的工具类,-_-||.
获取unionid需要满足的条件.
官方文档:
大致分两种方式:
这个方式比较简单 , ,但是条件比较苛刻 , ,不过只要授权过,以后就会都用这个方法了 . 但是下面这种方式是[干净用户] 第一次必须走的方式:
如果获取解密数据就不啰嗦了,去看微信小程序官方API解释: 有两种方式可以获取.
emmmmmmmmmmmmmmm,,,,,,,,,,还是说明下吧,防止我忘了....
这个就比较火了,这个改动引发了微信社区的大暴动mmmmmmmmmm...
意思就是只要授权过才能用这个接口获取,如果没有授权直接调取这个接口及直接进fail函数.
方式2:用法点击看API
写一个button, 必须带图中的两个属性 , 点击button 会触发"onGotUserInfo function(), 返回的 信息就包括我们需要的数据:
格式比较恶心: 他在返回数据中的detail对象的字段中.
--------------------------------------------------这是两种方式返回的格式-----------------------------------------------------
接下来就是获取unionid的流程了:
第一种方式简单
第二种需要去解密:
需要三个数据:
session_key 这个是从解析 wx.login给的code 获取的 ,下面有代码
encryptedData 这个不过多解释
iv 这个也是直接拿到的
直接贴代码:
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.jd.ecc.commons.web.model.RespData;
import com.jd.ecc.tianfu.config.SysConstants;
import com.jd.ecc.tianfu.service.WeChatService;
import com.jd.ecc.tianfu.tools.HttpClientUtils;
import com.jd.ecc.tianfu.utils.AES;
@Service
public class WeChatServiceImpl implements WeChatService {
private Gson gson = new Gson();
private static final Logger LOG = LoggerFactory.getLogger(WeChatServiceImpl.class);
/**
* 1 先按照loginInfo信息查询有无有unionID
* 2 如果有,直接返回 ,程序结束
* 3 如果没有,将用于解密的session_key获取
* 4 根据userInfo拿取encryptedData等加密数据
* 5 对加密数据进行逆解密
* 6 拿到unionID 返回,程序结
*
* userInfo TODO 注意一下:通过user.getInfo获取的和通过button授权获取的格式不一样
*
*/
@Override
public RespData getUnionID(Long platformId, String userInfo, String loginInfo, HttpServletRequest request) {
Map resLogign = null;
JSONObject resUserInfo = null;
LOG.info("--------------->>>>>进入WeChatServiceImpl");
resLogign = getUnionIDByLoginInfo(loginInfo, request);
// 加密session_key 解密需要
String session_key = resLogign.get("session_key");
if (StringUtils.isNotBlank(resLogign.get("unionid"))) {
LOG.info("返回数据,获取unionid,程序结束<<<------unionid={}", resLogign.get("unionid"));
return RespData.success("获取unionid成功,返回数据!", resLogign);
} else {
LOG.info("没有获取到unionid,userinfo进行对称解密操作start----->");
resUserInfo = decipherByUserInfo(session_key, userInfo, request);
if (resUserInfo != null && StringUtils.isNotEmpty(resUserInfo.toJSONString())) {
LOG.info("解密后的信息为resUserInfo:{}", resUserInfo.toJSONString());
// 解密信息由 data.get("unionid") 获取
return RespData.success("获取解密信息成功", resUserInfo);
}
}
return null;
}
我特意拆成了两个方法:
看方法名字就能看出来了:
/**
*
* @param session_key
* @param userInfo
* @param request
* @return
*/
private JSONObject decipherByUserInfo(String session_key, String userInfo, HttpServletRequest request) {
LOG.info("--------------->>>>>进入 decipherByUserInfo");
Map resUserInfo = new HashMap<>();
if (!userInfo.contains("login:ok")&&!userInfo.contains("getUserInfo:ok")) {
return null;
}
JSONObject jsonUserInfo = JSONObject.parseObject(userInfo);
LOG.info("将userInfo转换成JSON,userInfo:{}", jsonUserInfo.toJSONString());
String encryptedData = "";
String iv = "";
if (jsonUserInfo.get("detail") != null && StringUtils.isNotBlank(jsonUserInfo.get("detail").toString())) {
String detail = jsonUserInfo.get("detail").toString();
JSONObject parseObject = JSONObject.parseObject(detail);
encryptedData = (String) parseObject.get("encryptedData");
iv = (String) parseObject.get("iv");
} else {
encryptedData = (String) jsonUserInfo.get("encryptedData");
iv = (String) jsonUserInfo.get("iv");
}
LOG.info("获取解密数据解密start------------>>>解密需要的数据为:encryptedData:{},iv:{},session_key:{}", encryptedData, iv, session_key);
// 被加密的数据
byte[] dataByte = Base64.decodeBase64(encryptedData);
// 加密秘钥
byte[] aeskey = Base64.decodeBase64(session_key);
// 偏移量
byte[] ivByte = Base64.decodeBase64(iv);
LOG.info("对加密数据进行Base64编码完毕------------>>>dataByte:{},aeskey:{},ivByte:{}", dataByte.toString(), aeskey.toString(),ivByte.toString());
String newuserInfo = "";
try {
AES aes = new AES();
byte[] resultByte = aes.decrypt(dataByte, aeskey, ivByte);
if (null != resultByte && resultByte.length > 0) {
newuserInfo = new String(resultByte, "UTF-8");
LOG.info("解密完毕,解密结果为newuserInfo:{}", newuserInfo);
return JSONObject.parseObject(newuserInfo);
}
} catch (Exception e) {
LOG.info("解密异常!检查解密数据 {}", newuserInfo, e);
e.printStackTrace();
}
return null;
}
/**
* 根据logininfo 的code尝试获取unionID
*
* @param loginInfo
* @param request
* @return
*/
private Map getUnionIDByLoginInfo(String loginInfo, HttpServletRequest request) {
JSONObject loginRes = null;
Map loginReq = new HashMap<>();
Map loginResMap = new HashMap<>();
LOG.info("--------------->>>>>进入 getUnionIDByLoginInfo");
JSONObject loginMap = JSONObject.parseObject(loginInfo);
LOG.info("转换JSON完毕");
LOG.info("组装请求数据----start:");
String resCode = (String) loginMap.get("code");
loginReq.put("js_code", resCode);
loginReq.put("appid", SysConstants.WECHAT_APPID); // 这几个变量是自己去获取的 我做成了配置文件中
loginReq.put("secret", SysConstants.WECHAT_SECRET); // 去这个网址看参数说明: https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html#wxloginobject
loginReq.put("grant_type", SysConstants.WECHAT_FRANT_TYPE);
LOG.info("组装请求参数完毕loginReq={}", gson.toJson(loginReq));
try {
LOG.info("开始发送请求到微信...url={}", SysConstants.WEICHAT_UNIONID_URL);
loginRes = HttpClientUtils.doGet(SysConstants.WEICHAT_UNIONID_URL, loginReq);
} catch (Exception e) {
LOG.error("调用httpClient异常!异常信息={}", e);
throw new RuntimeException(e);
}
String openid = (String) loginRes.get("openid");
String session_key = (String) loginRes.get("session_key");
String unionid = (String) loginRes.get("unionid");
loginResMap.put("openid", openid);
loginResMap.put("unionid", unionid);
loginResMap.put("session_key", session_key);
LOG.info("https返回数据loginRes={}", loginRes.toJSONString());
// 微信建议不要在传输中有session_key,我们在这里做一个缓存数据key=session_key_safe,value="真实的session_key+真实的openid"
// TODO
// 时间问题,这里暂不处理
// loginResMap.put("session_key", "session_key_safe");
return loginResMap;
}
然后是
AES.java 其实我也是从网上找的 不过亲测可用.
package com.jd.ecc.tianfu.utils;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
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 org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AES {
public static boolean initialized = false;
/**
* AES对称解密工具类
*
* @param content
* 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
// java是没有
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void initialize() {
if (initialized)
return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
// 生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
以上.
还是贴一下我返回的数据吧: