微信小程序登录 后端java
微信小程序对于2021年4月1日还未发布的小程序,前端根据wx.getUser得到的数据已无法解析到用户信息。更改前端调用接口为wx.getUserProFile,需要用户授权后才可获得用户相关信息。
小程序前端调用wx.login接口可获得code
小程序前端调用wx.getUserProFile 可获得encryptedData 和 iv
java后端在获取到对应的数据后访问:https://api.weixin.qq.com/sns/jscode2session换取openid和session_key(用于对数据解码),然后对数据进行解码。
代码如下:
@RequestMapping(value = "login.html")
public ResponseVO login(HttpServletRequest request) {
String encryptedData = request.getParameter("encryptedData");
String iv = request.getParameter("iv");
String code = request.getParameter("code");
if (StringTool.checkedString(encryptedData, iv, code)) {
return ResponseVO.failure("请同意微信读取相关信息!");
}
String appid = "小程序appId";
String appSecret = "小程序对应的appSecret";
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";
String responseBody = HttpsUtil.doGet(apiUrl);
Map<String, String> jsonObject = new Gson().fromJson(responseBody, Map.class);
if (!StringTool.checkedString(jsonObject.get("openid")) && !StringTool.checkedString(jsonObject.get("session_key"))) {
//查询用户是否存在
PlayerInfo playerInfo = playerInfoService.getMemberByUId(jsonObject.get("openid"));
if (playerInfo == null) {
//解密获取用户信息
JSONObject userInfoJSON = WechatGetUserInfoUtil.getUserInfo(encryptedData, jsonObject.get("session_key"), iv);
if (userInfoJSON != null) {
playerInfo = new PlayerInfo();
playerInfo.setUNickName(userInfoJSON.get("nickName").toString());//昵称
playerInfo.setUId(jsonObject.get("openid"));//openId
playerInfo.setUCity(userInfoJSON.get("city").toString());//城市
playerInfo.setUSex(Integer.parseInt(userInfoJSON.get("gender").toString()));//性别
playerInfo.setULogo(userInfoJSON.get("avatarUrl").toString());//头像
playerInfoService.addPlayer(playerInfo);
}
}
return ResponseVO.success(playerInfo);
}
return ResponseVO.failure("登录失败!");
}
HttpsUtil代码如下:
import java.io.IOException;
import java.util.Map;
import com.google.gson.Gson;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
public class HttpsUtil {
public static String doGet(String url) {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpGet httpget = new HttpGet(url);
String jsonObj = null;
try {
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
String result = EntityUtils.toString(entity, "UTF-8");
jsonObj = result;
}
} catch (IOException e) {
e.printStackTrace();
}
return jsonObj;
}
public static Map<String, Object> doPost(String url, String json) {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost(url);
Map<String, Object> jsonObj = null;
try {
StringEntity stringEntity = new StringEntity(json, "UTF-8");
httpPost.setEntity(stringEntity);
httpPost.addHeader("Accept", "application/json");
httpPost.setHeader("content-type", "application/json");
HttpResponse response = httpclient.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity != null) {
String result = EntityUtils.toString(entity, "UTF-8");
jsonObj = new Gson().fromJson(result, Map.class);
}
} catch (IOException e) {
e.printStackTrace();
}
return jsonObj;
}
}
StringTool代码如下:
/**
* @version v1.0
* @Description: TODO(字符串检测工具类)
* @Author: jyj
* @Date: 2020/3/25 0025 11:05
*/
public class StringTool {
/***
* TODO:检测String是否有为空的情况
*
* @param strings
* @return
*/
public static boolean checkedString(String... strings) {
for (String str : strings) {
if (str == null || str.trim().length() == 0 || str.equals("undefined")) {
return true;
}
}
return false;
}
}
WechatGetUserInfoUtil代码如下:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;
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;
import java.util.Arrays;
import java.util.Base64;
/**
* @author lqx
* @create 2018-08-07 8:30
*/
public class WechatGetUserInfoUtil {
//日志记录器
private static final Logger log = LoggerFactory.getLogger(WechatGetUserInfoUtil.class);
/**
* 解密用户敏感数据获取用户信息
*
* @param sessionKey 数据进行加密签名的密钥
* @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
* @param iv 加密算法的初始向量
* @return
*/
public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
// 被加密的数据
byte[] dataByte = Base64Utils.decode(encryptedData.getBytes());
// 加密秘钥
byte[] keyByte = Base64Utils.decode(sessionKey.getBytes());
// 偏移量
byte[] ivByte = Base64Utils.decode(iv.getBytes());
try {
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
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, "UTF-8");
return JSON.parseObject(result);
}
} catch (NoSuchAlgorithmException e) {
log.error(e.getMessage(), e);
} catch (NoSuchPaddingException e) {
log.error(e.getMessage(), e);
} catch (InvalidParameterSpecException e) {
log.error(e.getMessage(), e);
} catch (IllegalBlockSizeException e) {
log.error(e.getMessage(), e);
} catch (BadPaddingException e) {
log.error(e.getMessage(), e);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (InvalidKeyException e) {
log.error(e.getMessage(), e);
} catch (InvalidAlgorithmParameterException e) {
log.error(e.getMessage(), e);
} catch (NoSuchProviderException e) {
log.error(e.getMessage(), e);
}
return null;
}
}
以上代码居多,不喜原理,能看到这都是遇到问题才来的。其实原理很简单,就是通过前端传入的encryptedData已经包含了对应的用户数据,只是需要重新通过拿到session_key对数据进行解密而已。如有缺少相关类的请留言。