目录
- 前言
- 小程序登录
- 步骤说明
- 前端
- 效果
- 涉及到的接口
- 登录凭证:wx.login
- 获取用户信息:wx.getUserInfo
- 后端
- 涉及到接口
- 小程序登录
- 代码展示
- 微信扫码登录
前言
微信官方文档,需要对接哪个模块就从哪里进入。
由于本次我们需要的是小程序的登录。所以就选择第一个。
介绍一下 API
和 服务端
:
小程序登录
步骤说明
小程序登录步骤:
- 开发者客户端调用 wx.login() 获取
临时登录凭证code
,并回传到开发者服务器。 - 开发者服务器 调用 auth.code2Session 接口,换取
用户唯一标识 OpenID
、用户在微信开放平台账号下的唯一标识UnionID
(若当前小程序已绑定到微信开放平台账号) 和会话密钥 session_key
。
如果需要的话,开发者客户端 可以调用 wx.getUserInfo ,获取到 code
和 encryptedData
, iv
,再传回给 开发者服务器,去进行解密,解密成功后就可以拿到 用户信息。
前端
效果
用户信息功能页中注明:
用户信息功能页用于帮助插件获取用户信息,包括 openid 和昵称等,相当于 wx.login
和 wx.getUserInfo
的功能。
在基础库版本 2.3.1 前,用户信息功能页是插件中唯一的获取 code 和用户信息的方式;
自基础库版本 2.3.1 起,用户在该功能页中进行过授权后,插件可以直接调用 wx.login 和 wx.getUserInfo:
涉及到的接口
登录凭证:wx.login
获取用户信息:wx.getUserInfo
后端
以Java代码为展示。
涉及到接口
小程序登录
代码展示
可以参考链接:https://blog.51cto.com/u_16099229/7857781
从前面可以得到,前端会返回给后端一些数据。所以,就新建一个实体类来进行接受数据。
@Data
public class WechatMPLoginParams {
/**
* uuid 用户uuid
* code 微信返回code 用于与微信交互获取openid 等信息
* encryptedData 微信返回加密信息
* iv 微信返回
* image 微信头像
* nickname 微信用户昵称
*/
private String uuid, code, encryptedData, iv, image, nickName;
}
yaml配置
wechat:
AppID:
AppSecret:
依赖
<hutool-all.version>5.7.18</hutool-all.version>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
控制层
/**
* 小程序自动登录
*
* @param params 请求参数
* @return
*/
@PostMapping("/login/auto-login")
@ApiOperation(value = "小程序自动登录")
public AjaxResult autoLogin(@RequestBody WechatMPLoginParams params) {
return AjaxResult.success(connectService.miniProgramAutoLogin(params));
}
业务层
@Slf4j
@Service
public class ConnectServiceImpl extends ServiceImpl<ConnectMapper, Connect> implements ConnectService {
static final boolean AUTO_REGION = true;
@Value("${wechat.AppID}")
private String WECHAT_APPID;
@Value("${wechat.AppSecret}")
private String WECHAT_APPSECRET;
@Override
@Transactional
public String miniProgramAutoLogin(WechatMPLoginParams params) {
HashMap cacheData = (HashMap) cache.get(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());
Map<String, String> map = new HashMap<>(3);
// 1. 这里最重要的是获取到 session key、unionId 、openId
// 先从缓存中,查看是否已经保存了用户小程序登录的信息
if (StringUtils.isEmpty(cacheData)) {
// 得到微信小程序联合登陆信息,并存进缓存里面
getWechatMsgByCache(params, map);
} else {
// 直接从缓存中获取到微信小程序联合登陆信息
map = (Map<String, String>) cacheData;
// System.out.println("cache: " + map.get("openId"));
}
// 2. 微信联合登陆
return phoneMpBindAndLogin(map, params, map.get("openId"), map.get("unionId"));
}
/**
* 微信小程序联合登陆信息并存储session key、unionId 、openId信息
*
* @param params 参数
* @param map 联合登录信息
*/
private void getWechatMsgByCache(WechatMPLoginParams params, Map<String, String> map) {
// System.out.println(params.getCode());
JSONObject json = this.getConnect(params.getCode());
// System.out.println(json);
// 存储session key、unionId 、openId 后续登录用得到
String sessionKey = json.getStr("session_key");
String unionId = json.getStr("unionid");
String openId = json.getStr("openid");
map.put("sessionKey", sessionKey);
map.put("unionId", unionId);
map.put("openId", openId);
cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900L);
}
/**
* 通过微信返回等code 获取openid 等信息
*
* @param code 微信code
* @return 微信返回的信息
*/
public JSONObject getConnect(String code) {
// WechatConnectSettingItem setting = getWechatMPSetting();
String url = "https://api.weixin.qq.com/sns/jscode2session?" +
"appid=" + WECHAT_APPID + "&" +
"secret=" + WECHAT_APPSECRET + "&" +
"js_code=" + code + "&" +
"grant_type=authorization_code";
// log.info("微信返回等code 获取openid 等信息 " + url);
String content = HttpUtils.doGet(url, "UTF-8", 100, 1000);
// log.info(content);
return JSONUtil.parseObj(content);
}
@Transactional(rollbackFor = Exception.class)
public String phoneMpBindAndLogin(Map<String, String> map, WechatMPLoginParams params) {
String sessionKey = map.get("sessionKey");
String encryptedData = params.getEncryptedData();
String iv = params.getIv();
String unionId = map.get("unionId");
String openId= map.get("openId");
// 1. 解密获取用户信息
JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);
// log.info("联合登陆返回:{}", userInfo.toString());
// 2. 通过解密获得的电话号码查询是否存在该用户
String phone = (String) userInfo.get("purePhoneNumber");
SysUser user = new SysUser();
List<SysUser> userList = sysUserService.selectUserInfoList(user);
// 2.1 不存在该用户
if (StringUtils.isEmpty(userList)) {
throw new Exception("非员工手机号码,不允许进行微信绑定登录!");
}
// 2.2 存在该用户
LoginUser loginUser = new LoginUser();
SysUser sysUser = userList.get(0) ;
BeanUtils.copyProperties(sysUser, loginUser);
loginUser.setUser(sysUser);
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setopenid(openId);
sysUser.setUnionId(unionId);
sysUserService.updateUserProfile(sysUser);
// 3. 生成token
return myTokenService.createToken(loginUser);
}
/**
* 解密,获取微信信息
*
* @param encryptedData 加密信息
* @param sessionKey 微信sessionKey
* @param iv 微信揭秘参数
* @return 用户信息
*/
public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);
//被加密的数据
byte[] dataByte = Base64.getMimeDecoder().decode(encryptedData);
//加密秘钥
byte[] keyByte = Base64.getMimeDecoder().decode(sessionKey);
//偏移量
byte[] ivByte = Base64.getMimeDecoder().decode(iv);
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, StandardCharsets.UTF_8);
return JSONUtil.parseObj(result);
}
} catch (Exception e) {
log.error("解密,获取微信信息错误", e);
}
throw new ServiceException("联合第三方登录,授权信息错误", 20012);
}
}
功能类
package com.higentec.system.common.utils.http;
import java.io.*;
import java.net.*;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.higentec.system.common.constant.Constants;
import com.higentec.system.common.utils.StringUtils;
/**
* 通用http发送方法
*
* @author higentec
*/
public class HttpUtils
{
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url)
{
return sendGet(url, StringUtils.EMPTY);
}
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param)
{
return sendGet(url, param, Constants.UTF8);
}
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @param contentType 编码类型
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param, String contentType)
{
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try
{
String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
log.info("sendGet - {}", urlNameString);
URL realUrl = new URL(urlNameString);
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(), contentType));
String line;
while ((line = in.readLine()) != null)
{
result.append(line);
}
log.info("recv - {}", result);
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
}
finally
{
try
{
if (in != null)
{
in.close();
}
}
catch (Exception ex)
{
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* 向指定 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;
StringBuilder result = new StringBuilder();
try
{
String urlNameString = url;
log.info("sendPost - {}", urlNameString);
URL realUrl = new URL(urlNameString);
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)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null)
{
result.append(line);
}
log.info("recv - {}", result);
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
}
finally
{
try
{
if (out != null)
{
out.close();
}
if (in != null)
{
in.close();
}
}
catch (IOException ex)
{
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
public static String sendSSLPost(String url, String param)
{
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
try
{
log.info("sendSSLPost - {}", urlNameString);
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(urlNameString);
HttpsURLConnection conn = (HttpsURLConnection) console.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)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ret = "";
while ((ret = br.readLine()) != null)
{
if (ret != null && !"".equals(ret.trim()))
{
result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
}
}
log.info("recv - {}", result);
conn.disconnect();
br.close();
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
}
return result.toString();
}
private static class TrustAnyTrustManager implements X509TrustManager
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[] {};
}
}
/**
* get 请求 静态方法
*
* @param link
* @param encoding
* @return
*/
public static String doGet(String link, String encoding, int connectTimeout, int readTimeout) {
HttpURLConnection conn = null;
try {
URL url = new URL(link);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
BufferedInputStream in = new BufferedInputStream(
conn.getInputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (int i = 0; (i = in.read(buf)) > 0; ) {
out.write(buf, 0, i);
}
out.flush();
String s = out.toString(encoding);
return s;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
}
如果需要单独从微信小程序获取用户手机号码,可参考:
- 微信开发文档—— 获取手机号
- 详细教程:微信小程序获取用户手机号码教程(前端+后端):https://blog.csdn.net/qq_51235856/article/details/131158254
微信扫码登录
微信登录——授权登录获取用户信息:https://blog.csdn.net/YXXXYX/article/details/127338450