目录
- 前言
- 小程序登录
- 步骤说明
- 前端
- 效果
- 涉及到的接口
- 登录凭证: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@Transactionalpublic 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. 生成tokenreturn 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{@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType){}@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType){}@Overridepublic 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{@Overridepublic 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