想要获取unionId,同一主体下必须有几个小程序或者微信官方账号,否则就没有了unionId,只生成用户openid,可登录以下微信官方平台查看
https://open.weixin.qq.com
注:用户未注意官方账号前端常规调用wx.login()得不到unionid,
解决方案(仅供参考,实际测量有效):在满足上述条件的情况下,前端先转移wx.login后得到SessionKey
前端再调用wx.getUserInfo接口(此时带登录态)
返回给后端wx.getUserInfo在接口中获取vi和encryptedData SessionKey即可获取unionId
使用weixin-java-miniapp实现微信小程序登录接口 个人认为代码最整洁
maven 依赖
<!--微信开源包装sdk--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>3.6.0</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
PS:不想导入lombok请给LoginRequest的字段加上getter/setter方法 去掉@Data
@Data public class LoginRequest { ///用户登录凭证 String code;
///原始数据字符串 String signature;
/// String rawData;
//加密用户数据 String encryptedData;
////加密算法的初始向量 String iv; } 主要代码
import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import me.chanjar.weixin.common.error.WxErrorException;
/** * @describe: 类描述信息 * @author: zwr * @date: 2020/3/26/13:48 PM */ public class WxAppLoginService {
private WxMaService getWxMaService() { WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); config.setAppid( ResourceUtil.getConfigByName("wx.appId")); ///这是你自己的小程序appid, config.setSecret(ResourceUtil.getConfigByName("wx.secret")); config.setMsgDataFormat("JSON"); WxMaService wxMaService = new WxMaServiceImpl(); wxMaService.setWxMaConfig(config); return wxMaService; }
public void login(LoginRequest request) throws WxErrorException { final WxMaService wxService = getWxMaService();
// 获取微信用户session WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(request.getCode()); if (null == session) { throw new RuntimeException("login handler error"); }
// 解密用户信息 WxMaUserInfo wxUserInfo = wxService.getUserService().getUserInfo(session.getSessionKey(), request.getEncryptedData(), request.getIv()); if (null == wxUserInfo) { throw new RuntimeException("获取失败"); } String unionId = wxUserInfo.getUnionId();
}
}
在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;br> 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; } } 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;
public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv)); return params;
} /** * 获得对明文进行补位填充的字节. * * @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; } } 处理业务逻辑
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("微信登陆发生异常"); } } }
第三种方式: 无需导入第三方jar
一、前端需传递参数 iv encryptedData signature rawData
package com.platform.entity;
import java.io.Serializable;
/**
* @author zwr
* @email 1064562445@qq.com
* @date 2017-08-15 08:03:41
*/
public class FullUserInfo implements Serializable {
private static final long serialVersionUID = 1L;
//errMsg
private String errMsg;
//rawData
private String rawData;
//encryptedData
private String encryptedData;
//iv
private String iv;
//signature
private String signature;
}
/**
* 登录
*/
@ApiOperation(value = "登录")
@IgnoreAuth
@RequestMapping("/app/loginWeChat")
@Transactional(rollbackFor = Exception.class)
public Object loginByWeixin() {
JSONObject jsonParam = this.getJsonRequest();
logger.info("***请求参数=" + jsonParam.toString());
FullUserInfo fullUserInfo = null;
String code = "";
if (!StringUtils.isNullOrEmpty(jsonParam.getString("code"))) {
code = jsonParam.getString("code");
}
if (null != jsonParam.get("data")) {
fullUserInfo = jsonParam.getObject("data", FullUserInfo.class);
}
if (null == fullUserInfo) {
return R.error("登录失败");
}
Map<String, Object> resultObj = new HashMap<>();
//获取openid //小程序appid修改做修改 // logger.info("》》》组合token为:" + requestUrl);
String requestUrl = ApiUserUtils.getWebAccess(code);
//通过自定义工具类组合出小程序需要的登录凭证 code //System.out.println(sessionData.getString("openid"));
JSONObject sessionData = CommonUtil.httpsRequest(requestUrl, "GET", null);
if (null == sessionData || StringUtils.isNullOrEmpty(sessionData.getString("openid"))) {
return R.error("登录失败");
}
String uuid ="";
if(StringUtils.isNullOrEmpty(sessionData.getString("unionid"))){
uuid= sessionData.get("unionid").toString();
}
}
工具类
wx.webAccessTokenhttps = https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code
package com.platform.util;
import com.platform.utils.ResourceUtil;
/**
* 作者: @author zwr <br>
* 时间: 2017-08-11 08:58<br>
* 描述: ApiUserUtils <br>
*/
public class ApiUserUtils {
//替换字符串
public static String getCode(String APPID, String REDIRECT_URI, String SCOPE) {
return String.format(ResourceUtil.getConfigByName("wx.getCode"), APPID, REDIRECT_URI, SCOPE);
}
//替换字符串 验证appid和secret的正确性
public static String getWebAccess(String CODE) {
return String.format(ResourceUtil.getConfigByName("wx.webAccessTokenhttps"),
ResourceUtil.getConfigByName("wx.appId"),
ResourceUtil.getConfigByName("wx.secret"),
CODE);
}
//替换字符串
public static String getUserMessage(String access_token, String openid) {
return String.format(ResourceUtil.getConfigByName("wx.userMessage"), access_token, openid);
}
}
package com.platform.util;
import com.alibaba.fastjson.JSONObject;
import com.platform.utils.CharUtil;
import com.platform.utils.DateUtils;
import com.platform.utils.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CommonUtil {
/**
* 发送https请求
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过JSONObject.get ( key)的方式获取json对象的属性值)
*/
public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {new MyX509TrustManager()};
/*
该处不对ssl证书进行验证,以免测试不通过
*/
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
//从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
jsonObject = JSONObject.parseObject(buffer.toString());
} catch (ConnectException ce) {
log.error("连接超时:{}", ce);
} catch (Exception e) {
log.error("https请求异常:{}", e);
}
return jsonObject;
}
}
转载注明出处,掌声送给社会人