流程梳理:
code - 请求微信接口 - 获得session_key - AES解密 - 获得信息
小程序端通过open-tye(具体百度),用户授权后,返回code,iv,encrypteData传参到后台,通过微信小程序接口(https://api.weixin.qq.com/sns/jscode2session?appId=" + xgxAppid + "&secret=" + xgxSecret + "&js_code=" + code + "&grant_type=" + grant_type)请求微信,获取会话密钥(session_key),通过AES对称解密(
AesMiniProgramUtil.decrypt(encryptedData, session_key, iv, "UTF-8")
),获得用户基本信息
小坑:
code得有效期为5分钟。
在开发阶段,创建小程序项目的时候的appId必须和解密时的appId对应,因为获取用户code的时候时通过appid去获取的,得到的加密数据时通过appid加密的,AES解密的时候得对应。并且得设置体验者权限。
上代码:
需要导入的maven
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
接口:
/**
* 获取小程序用户信息
*
* @param codeDto
* @return
*/
@RequestMapping(value = "miniprogram")
@ResponseBody
@Transactional(readOnly = false)
public Object getMiniProgramUserInfo(HttpServletResponse response, HttpServletRequest request, Model model, @RequestBody WxCodeDto codeDto) {
//response.setHeader("Access-Control-Allow-Origin", "*");
try {
logger.info("miniprogram WxCodeDto >> " + codeDto);
Map<String, String> unionIdForXgx = WeixinKit.getUnionIdForXgx(codeDto.getEncryptedData(), codeDto.getIv(), codeDto.getCode(), "wx4533db2ce8b7c8ff", "f0037f13c01b49f1ae66d25e8a5df79f");
logger.info("miniprogram userinfo >> " + unionIdForXgx);
if(Integer.parseInt(unionIdForXgx.get("status"))==1) {
return DtoUtils.setSuccess("录入数据成功", unionIdForXgx);
}else {
return DtoUtils.setError("解密失败");
}
} catch (Exception e) {
return DtoUtils.setError("请求微信服务端异常");
}
}
工具类:
import com.alibaba.fastjson.JSONObject;
import com.thinkgem.jeesite.common.mapper.JsonMapper;
import com.thinkgem.jeesite.pc.web.WeiXin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
/**
* Created by weck on 2017/2/27
*/
public class WeixinKit {
/**
* 日志对象
*/
protected Logger log = LoggerFactory.getLogger(WeixinKit.class);
/**
* 小程序使用授权getUserInfo获取unionID的加密数据,这里是解密操作
* 官方文档https://developers.weixin.qq.com/miniprogram/dev/api/open.html#wxgetuserinfoobject
* https://developers.weixin.qq.com/miniprogram/dev/api/signature.html
* @param encryptedData
* @param iv
* @param code
* @param xgxAppid
* @param xgxSecret
* @return
*/
public static Map<String,String> getUnionIdForXgx(String encryptedData, String iv, String code, String xgxAppid, String xgxSecret)throws Exception {
Map map = new HashMap();
if (StringUtils.isNotBlank(encryptedData) && StringUtils.isNotBlank(iv) && StringUtils.isNotBlank(code) && StringUtils.isNotBlank(xgxAppid) && StringUtils.isNotBlank(xgxSecret)) {
String grant_type = "authorization_code";
String url = "https://api.weixin.qq.com/sns/jscode2session?appId=" + xgxAppid + "&secret=" + xgxSecret + "&js_code=" + code + "&grant_type=" + grant_type;
String jsonCode = sendGet(url, null);//请求获得token
WeiXin resDto = (WeiXin) JsonMapper.fromJsonString(jsonCode, WeiXin.class);//获得json格式,转换实体
if (resDto!=null) {
String session_key = resDto.getSession_key();
try {
String result = AesMiniProgramUtil.decrypt(encryptedData, session_key, iv, "UTF-8");
if (null != result && result.length() > 0) {
JSONObject userInfoJSON = JSONObject.parseObject(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);
map.put("status", 1);
map.put("msg", "解密成功");
return map;
}
} catch (Exception e) {
e.printStackTrace();
map.put("status", 0);
map.put("msg", "解密失败!");
return map;
}
map.put("status", 0);
map.put("msg", "解密失败!");
return map;
} else {
map.put("status", 0);
map.put("msg", "请求失败,请重试!");
}
} else {
map.put("status", 0);
map.put("msg", "请上传正确的参数!");
}
return map;
}
public static String sendGet(String url, String param) throws Exception {
String result = "";
BufferedReader in = null;
try {
URL realUrl = new URL(url);
// 打开和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();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
throw e2;
}
}
return result;
}
}
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;
/**
* @author Weck on 2018/06/28
* 小程序getUserInfo接口加密数据解密工具
*/
public class AesMiniProgramUtil {
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 {
//被加密的数据
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;
}
}
DTO:
public class WxCodeDto {
private String code;
private String encryptedData;
private String iv;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getEncryptedData() {
return encryptedData;
}
public void setEncryptedData(String encryptedData) {
this.encryptedData = encryptedData;
}
public String getIv() {
return iv;
}
public void setIv(String iv) {
this.iv = iv;
}
@Override
public String toString() {
return "WxCodeDto{" +
"code='" + code + '\'' +
", encryptedData='" + encryptedData + '\'' +
", iv='" + iv + '\'' +
'}';
}
}