说到这里真的很想吐槽这个微信官方文档了。结合后端开发语言的Demo中竟然没有java不想说了。截至到2018年7月9日。微信官方api依旧没有!呵呵呵呵呵呵,不多BB接下来给小伙伴们分享一下获取unioId的方法
一、首先清楚原理
UnionID获取途径
绑定了开发者帐号的小程序,可以通过下面3种途径获取UnionID。
调用接口wx.getUserInfo,从解密数据中获取UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。
如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。
如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过wx.login获取到该用户UnionID,无须用户再次授权
以上是微信小程序官方api其中后两种方式,表示看不懂,我们身为程序员就使用第一种方法
有的同学可能会说为什么我调用了wx.getUserInfo只能获取到基本的一些微信用户的信息,根本没有什么openId以及unionId?
因为。你需要在调用wx.login()成功后的回调函数中在调用wx.getUserInfo,你就会获得包含openId以及unionId的加密数据
不懂的同学看下面的小程序代码就明白了。
打印一下你拿到的数据,你就会发现这些数据全都是加密数据,我们必须用后端实现解密,才能拿到有价值的数据
二 、用自己的理解方式去做登陆
为什么要这么说,因为按照微信小程序的api只需要从小程序获得code用后端处理就能返回openId和session_key
这种方法很蠢。
下面我来解释一下我做微信登陆的方式
首先确定参数:
小程序端传递的数据为
code:微信小程序发送给我们后端的登陆验证code
encrypteData:包含unioId等敏感信息
初始向量
后端处理参数(非常重要!以java为例子)
这个地方有坑下面我来说一下传递这三个参数的时候会出现什么问题
code传递的时候,不会出问题,但是encrypteData和iv的传递的时候,因为它的加密算法原因,导致这两条密文会填充一些"+"符号。但是"+"在URL传递参数的时候,会被解析成空格 所以在后端处理参数的时候先替换掉所有参数的空格,用string.replaceAll(" ","+"); 将"+"符号还原回来,再进行业务处理。
三、解密操作
解密操作需要两个类。CSDN上面的论坛帖子都是一个不完全的流程瞎转载到处踩坑。本人踩遍无数次的坑之后呢,决定一定要把完整版的分享给大家,话不多说上工具类!
AES类
注意这个类!这行代码,你必须要用BC算法才可以。
Cipher cipher = Cipher.getInstance(ALGORITHM,"BC");
然后注意一下这个,如果你的项目框架中有这个类,一定要让他加载的时候最先加载。如果没有的话,你就在每次加密解密之前创建这个对象就好了
import org.bouncycastle.jce.provider.BouncyCastleProvider;
在maven中的pom引入为
<!--微信解密 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk15on</artifactId>
<version>1.55</version>
</dependency>
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;
import java.security.*;
public class AES {
/**
* AES解密
* @param content 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public static final String ALGORITHM = "AES/CBC/PKCS7Padding";
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
try {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM,"BC");
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;
}
}
第二个类WxPKCS7Encoder
import java.nio.charset.Charset;
import java.util.Arrays;
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;
}
}
三、怎么处理自己的业务逻辑
下面解释一下为什么我要写成这样的service业务逻辑,大家看完之后就会明白了~
package com.qujiali.service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.qujiali.mapper.TMemberMapper;
import com.qujiali.model.TMember;
import top.qujiali.core.Constants;
import top.qujiali.core.base.BaseService;
import top.qujiali.core.util.AES;
import top.qujiali.core.util.SecurityUtil;
import top.qujiali.core.util.WxPKCS7Encoder;
/**
* 微信小程序授权登陆
*
* @author ydl
* @date 2018年7月3日
*/
@Service
public class WxloginService extends BaseService<TMember> {
public Map wxLogin(String Code, String encryptedData, String iv) {
try {
//用map来封装想给前端返回的数据
Map map = new HashMap();
// 微信服务器的接口,其实就是微信官方文档上面的那一串url地址
String url = Constants.WECHATURLHEAD + Code + Constants.WECHATURLEDN;
// 服务端请求URL需要的Spring对象
RestTemplate restTemplate = new RestTemplate();
// 调用请求url
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
// 拿到消息体
String sessionData = responseEntity.getBody();
JSONObject json = JSON.parseObject(sessionData);
// 1.sessionkey取消空格这里就是特别坑的一个地方
String data = encryptedData.replaceAll(" ", "+");
String ivstr = iv.replaceAll(" ", "+");
String sessionkey = json.get("session_key").toString();
// 2.openId 取消空格补充"+"
String openId = json.get("openid").toString();
// 3、对encryptedData加密数据进行AES解密
AES aes = new AES();
byte[] resultByte = aes.decrypt(Base64.decodeBase64(data), Base64.decodeBase64(sessionkey),
Base64.decodeBase64(ivstr));
// 4.保存数据到服务器
if (null != resultByte && resultByte.length > 0) {
String userInfo = new String(WxPKCS7Encoder.decode(resultByte));
JSONObject wxinfo = JSON.parseObject(userInfo);
Set<String> keySet = wxinfo.keySet();
for (String key : keySet) {
//这里拿到key 和value就可以获取到解密后的所有数据了你可以做你自己的业务了,保存unioId或者是openId自己处理
}
}
}
return map;
} catch (Exception e) {
throw new RuntimeException("微信登陆发生异常");
}
}
}
好了~有不懂的小伙伴可以评论问我。