https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html 官方有个文档
简单来说,就是小程序端执行wx.login()方法获取临时登录凭证code,带着code访问我们的接口,接口用code访问微信的一个接口得到openid和秘钥session_key;
小程序端可以调用wx.getUserInfo这个方法 进行微信授权,用户点击同意授权后,可以获得一些用户数据,比如省市,性别等等,但其中一些数据是加密的。需要我们服务端写接口解密,解密就需要用到秘钥session_key
我只写了一个接口,参数有code和敏感信息,通过临时登录凭证code获取openid,和秘钥,再通过敏感信息和秘钥解密。
com.alibaba
fastjson
1.2.28
org.bouncycastle
bcprov-jdk16
1.46
commons-codec
commons-codec
1.4
net.sf.json-lib
json-lib
2.2.3
jdk15
需要自己下载微信开发者工具,工具中有自己的appid和secret(后面要用到),在项目设置中,调整成不校验url,
调用wx.login() 成功后再调用wx.getUserInfo 用户同意后,再携带得到的数据访问我们的接口
doLogin: function (callback = () => { }) {
let that = this;
wx.login({
success: function (loginRes) {
if (loginRes) {
//获取用户信息
wx.getUserInfo({
withCredentials: true,//非必填 默认为true
success: function (infoRes) {
console.log(infoRes, '>>>');
//请求服务端的登录接口
wx.request({
//url: 'http://localhost:8080/dl/weixinlogin',
url: 'http://192.168.1.108:8080/dl/weixinlogin',
data: {
code: loginRes.code,//临时登录凭证
rawData: infoRes.rawData,//用户非敏感信息
signature: infoRes.signature,//签名
encrypteData: infoRes.encryptedData,//用户敏感信息
iv: infoRes.iv,//解密算法的向量
appId:'写你自己的appid'
},
success: function (res) {
},
fail: function (error) {
//调用服务端登录接口失败
// that.showInfo('调用接口失败');
console.log(error);
}
});
}
});
} else {
}
}
});
},
在其中得到了openid和加密数据,可以自己打印出来
@ApiImplicitParams({
@ApiImplicitParam(name = "code", value = "code", dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "rawData", value = "非敏感信息\"", dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "signature", value = "签名", dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "encrypteData", value = "敏感加密信息", dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "iv", value = "偏移向量", dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "appId", value = "appId", dataType = "string", paramType = "query")
})
@ApiOperation(value="微信登录获取用户微信信息",notes="微信登录获取用户微信信息")
@ApiResponses(value = { @ApiResponse(code = 200, message = "Success") })
@RequestMapping(value="/weixinlogin",method=RequestMethod.GET)
public Message doLogin(
@RequestParam(value = "code",required = false) String code,
@RequestParam(value = "rawData",required = false) String rawData,
@RequestParam(value = "signature",required = false) String signature,
@RequestParam(value = "encrypteData",required = false) String encrypteData,
@RequestParam(value = "iv",required = false) String iv,
@RequestParam(value="appId",required=true)String appId){
JSONObject rawDataJson = JSON.parseObject( rawData );
JSONObject SessionKeyOpenId = getSessionKeyOrOpenId( code );
String openid = SessionKeyOpenId.getString("openid" );
String sessionKey = SessionKeyOpenId.getString( "session_key" );
//解密敏感信息
String decrypt = WXCore.decrypt(appId, encrypteData, sessionKey, iv);
JSONObject userInfo = JSON.parseObject(decrypt);
return Message.ok(decrypt);
}
访问微信接口 得到openid和秘钥session_key。 需要写自己开发者工具的appid和secret
public static JSONObject getSessionKeyOrOpenId(String code){
//微信端登录code
String wxCode = code;
String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
Map requestUrlParam = new HashMap( );
requestUrlParam.put( "appid"," " );//小程序appId
requestUrlParam.put( "secret"," " );
requestUrlParam.put( "js_code",wxCode );//小程序端返回的code
requestUrlParam.put( "grant_type","authorization_code" );//默认参数
//发送post请求读取调用微信接口获取openid用户唯一标识
JSONObject jsonObject = JSON.parseObject( UrlUtil.sendPost( requestUrl,requestUrlParam ));
return jsonObject;
}
用到的工具类:
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.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
/**
*
* @author Melo
*
*/
public class UrlUtil {
private static Logger log = Logger.getLogger(UrlUtil.class);
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, Map paramMap) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
String param = "";
Iterator it = paramMap.keySet().iterator();
while(it.hasNext()) {
String key = it.next();
param += key + "=" + paramMap.get(key) + "&";
}
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和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();
// 获取所有响应头字段
Map> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
}
package com.jubao.dling.common.util.weixin;
import org.apache.commons.codec.binary.Base64;
import net.sf.json.JSONObject;
/**
* 封装对外访问方法
* @author liuyazhuang
*
*/
public class WXCore {
private static final String WATERMARK = "watermark";
private static final String APPID = "appid";
/**
* 解密数据
* @return
* @throws Exception
*/
public static String decrypt(String appId, String encryptedData, String sessionKey, String iv){
String result = "";
try {
AES aes = new AES();
byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
if(null != resultByte && resultByte.length > 0){
result = new String(WxPKCS7Encoder.decode(resultByte));
JSONObject jsonObject = JSONObject.fromObject(result);
String decryptAppid = jsonObject.getJSONObject(WATERMARK).getString(APPID);
if(!appId.equals(decryptAppid)){
result = "";
}
}
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return result;
}
public static void main(String[] args) throws Exception{
String appId = "wx4f4bc4dec97d474b";
String encryptedData = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==";
String sessionKey = "tiihtNczf5v6AKRyjwEUhQ==";
String iv = "r7BXXKkLb8qrSNn05n0qiA==";
System.out.println(decrypt(appId, encryptedData, sessionKey, iv));
}
}
package com.jubao.dling.common.util.weixin;
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;
/**
* AES加密
* @author liuyazhuang
*
*/
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 {
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) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
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;
}
}
package com.jubao.dling.common.util.weixin;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* 微信小程序加解密
* @author liuyazhuang
*
*/
public class WxPKCS7Encoder {
private static final Charset CHARSET = Charset.forName("utf-8");
private static final int BLOCK_SIZE = 32;
/**
* 获得对明文进行补位填充的字节.
*
* @param count
* 需要进行填充补位操作的明文字节个数
* @return 补齐用的字节数组
*/
public static byte[] encode(int count) {
// 计算需要填充的位数
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 获得补位所用的字符
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted
* 解密后的明文
* @return 删除补位字符后的明文
*/
public static byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
/**
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
*
* @param a
* 需要转化的数字
* @return 转化得到的字符
*/
public static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}