作者 | 邓维-java
来源 | urlify.cn/aEB3Qj
66套java从入门到精通实战课程分享
微信小程序登录流程
微信小程序登录流程涉及到三个角色:小程序、开发者服务器、微信服务器
三者交互步骤如下:
第一步:小程序通过wx.login()获取code。
第二步:小程序通过wx.request()发送code到开发者服务器。
第三步:开发者服务器接收小程序发送的code,并携带appid、appsecret(这两个需要到微信小程序后台查看)、code发送到微信服务器。
第四步:微信服务器接收开发者服务器发送的appid、appsecret、code进行校验。校验通过后向开发者服务器发送session_key、openid。
第五步:开发者服务器自己生成一个skey(自定义登录状态)与openid、session_key进行关联,并存到数据库中(mysql、redis等)。
第六步:开发者服务器返回生成skey(自定义登录状态)到小程序。
第七步:小程序存储skey(自定义登录状态)到本地。
第八步:小程序通过wx.request()发起业务请求到开发者服务器,同时携带skey(自定义登录状态)。
第九步:开发者服务器接收小程序发送的skey(自定义登录状态),查询skey在数据库中是否有对应的openid、session_key。
第十步:开发者服务器返回业务数据到小程序。
yml:
<!--hutool具包--> <dependency> | |
<groupId>cn.hutool</groupId> | |
<artifactId>hutool-all</artifactId> | |
<version>.4.0</version> | |
</dependency> | |
<!--简化代码的工具包--> | |
<dependency> | |
<groupId>org.projectlombok</groupId> | |
<artifactId>lombok</artifactId> | |
<optional>true</optional> | |
</dependency> | |
<!-- mybatis-plus-spring-boot-starter--><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>.3.2</version></dependency> |
wx返回的用户信息:
/** | |
* @Author dw | |
* @ClassName WeChatUserInfo | |
* @Description 微信用户信息 | |
* @Date /8/28 14:14 | |
* @Version .0 | |
*/@Data | |
public class WeChatUserInfo { | |
/** | |
* 微信返回的code | |
*/ private String code; | |
/** | |
* 非敏感的用户信息 | |
*/ private String rawData; | |
/** | |
* 签名信息 | |
*/ private String signature; | |
/** | |
* 加密的数据 | |
*/ private String encrypteData; | |
/** | |
* 加密密钥 | |
*/ private String iv; | |
} |
WeChatUtil工具: | |
/** | |
* @Author dw | |
* @ClassName WeChatUtil | |
* @Description | |
* @Date/8/28 10:56 | |
* @Version.0 | |
*/public class WeChatUtil { | |
public static JSONObject getSessionKeyOrOpenId(String code) { | |
String requestUrl = "#;; | |
HashMap<String, Object> requestUrlParam = new HashMap<>(); | |
//小程序appId | |
requestUrlParam.put("appid", "小程序appId"); | |
//小程序secret | |
requestUrlParam.put("secret", "小程序secret"); | |
//小程序端返回的code | |
requestUrlParam.put("js_code", code); | |
//默认参数 | |
requestUrlParam.put("grant_type", "authorization_code"); | |
//发送post请求读取调用微信接口获取openid用户唯一标识 | |
String result = HttpUtil.get(requestUrl, requestUrlParam); | |
JSONObject jsonObject = JSONUtil.parseObj(result); | |
return jsonObject; | |
} | |
public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) throws BaseDecodingException { | |
// 被加密的数据 | |
byte[] dataByte = Base.decode(encryptedData); | |
// 加密秘钥 | |
byte[] keyByte = Base.decode(sessionKey); | |
// 偏移量 | |
byte[] ivByte = Base.decode(iv); | |
try { | |
// 如果密钥不足位,那么就补足. 这个if 中的内容很重要 | |
int base = ; | |
if (keyByte.length % base != ) { | |
int groups = keyByte.length / base + (keyByte.length % base != ? 1 : 0); | |
byte[] temp = new byte[groups * base]; | |
Arrays.fill(temp, (byte) ); | |
System.arraycopy(keyByte, , temp, 0, keyByte.length); | |
keyByte = temp; | |
} | |
// 初始化 | |
Security.addProvider(new BouncyCastleProvider()); | |
Cipher cipher = Cipher.getInstance("AES/CBC/PKCSPadding", "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 > ) { | |
String result = new String(resultByte, "UTF-"); | |
return JSONUtil.parseObj(result); | |
} | |
} catch (Exception e) { | |
} | |
return null; | |
} |
登录controller:
/** | |
* @Author dw | |
* @ClassName WeChatUserLoginController | |
* @Description | |
* @Date/8/28 14:12 | |
* @Version.0 | |
*/ | |
public class WeChatUserLoginController { | |
private IUserService userService; | |
/** | |
* 微信用户登录详情 | |
*/ | |
public ResultInfo user_login( WeChatUserInfo weChatUserInfo)throws BaseDecodingException { | |
//.开发者服务器 登录凭证校验接口 appId + appSecret + 接收小程序发送的code | |
JSONObject SessionKeyOpenId = WeChatUtil.getSessionKeyOrOpenId(weChatUserInfo.getCode()); | |
//.接收微信接口服务 获取返回的参数 | |
String openid = SessionKeyOpenId.get("openid", String.class); | |
String sessionKey = SessionKeyOpenId.get("session_key", String.class); | |
// 用户非敏感信息:rawData | |
// 签名:signature | |
JSONObject rawDataJson = JSONUtil.parseObj(weChatUserInfo.getRawData()); | |
//.校验签名 小程序发送的签名signature与服务器端生成的签名signature2 = sha1(rawData + sessionKey) | |
String signature = DigestUtils.sha1Hex(weChatUserInfo.getRawData() + sessionKey); | |
if (!weChatUserInfo.getSignature().equals(signature)) { | |
return ResultInfo.error( "签名校验失败"); | |
} | |
//encrypteData比rowData多了appid和openid | |
JSONObject userInfo = WeChatUtil.getUserInfo(weChatUserInfo.getEncrypteData(), | |
sessionKey, weChatUserInfo.getIv()); | |
//.根据返回的User实体类,判断用户是否是新用户,是的话,将用户信息存到数据库;不是的话,更新最新登录时间 | |
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); | |
userQueryWrapper.lambda().eq(User::getLoginName, openid); | |
int userCount = userService.count(userQueryWrapper); | |
// uuid生成唯一key,用于维护微信小程序用户与服务端的会话(或者生成Token) | |
String skey = UUID.randomUUID().toString(); | |
if (userCount <= ) { | |
// 用户信息入库 | |
String nickName = rawDataJson.get("nickName",String.class); | |
String avatarUrl = rawDataJson.get("avatarUrl",String.class); | |
String gender = rawDataJson.get("gender",String.class); | |
String city = rawDataJson.get("city",String.class); | |
String country = rawDataJson.get("country",String.class); | |
String province = rawDataJson.get("province",String.class); | |
// 新增用户到数据库 | |
} else { | |
// 已存在,更新用户登录时间 | |
} | |
//. 把新的skey返回给小程序 | |
return ResultInfo.success(); | |
} | |
} |
全局返回结果:
public class ResultInfo { | |
/** | |
* 响应代码 | |
*/ private String code; | |
/** | |
* 响应消息 | |
*/ private String message; | |
/** | |
* 响应结果 | |
*/ private Object result; | |
public ResultInfo() { | |
} | |
public ResultInfo(BaseErrorInfoInterface errorInfo) { | |
this.code = errorInfo.getResultCode(); | |
this.message = errorInfo.getResultMsg(); | |
} | |
public String getCode() { | |
return code; | |
} | |
public void setCode(String code) { | |
this.code = code; | |
} | |
public String getMessage() { | |
return message; | |
} | |
public void setMessage(String message) { | |
this.message = message; | |
} | |
public Object getResult() { | |
return result; | |
} | |
public void setResult(Object result) { | |
this.result = result; | |
} | |
/** | |
* 成功 | |
* | |
* @return | |
*/ public static ResultInfo success() { | |
return success(null); | |
} | |
/** | |
* 成功 | |
* @param data | |
* @return | |
*/ public static ResultInfo success(Object data) { | |
ResultInfo rb = new ResultInfo(); | |
rb.setCode(CommonEnum.SUCCESS.getResultCode()); | |
rb.setMessage(CommonEnum.SUCCESS.getResultMsg()); | |
rb.setResult(data); | |
return rb; | |
} | |
/** | |
* 失败 | |
*/ public static ResultInfo error(BaseErrorInfoInterface errorInfo) { | |
ResultInfo rb = new ResultInfo(); | |
rb.setCode(errorInfo.getResultCode()); | |
rb.setMessage(errorInfo.getResultMsg()); | |
rb.setResult(null); | |
return rb; | |
} | |
/** | |
* 失败 | |
*/ public static ResultInfo error(String code, String message) { | |
ResultInfo rb = new ResultInfo(); | |
rb.setCode(code); | |
rb.setMessage(message); | |
rb.setResult(null); | |
return rb; | |
} | |
/** | |
* 失败 | |
*/ public static ResultInfo error(String message) { | |
ResultInfo rb = new ResultInfo(); | |
rb.setCode("-"); | |
rb.setMessage(message); | |
rb.setResult(null); | |
return rb; | |
}} |
微信小程序
项目结构:
项目结构
1 初始配置
初始配置
2 me.wxml
<view class="container"> | |
<!-- 登录组件 --> | |
<button wx:if="{{!hasUserInfo}}" open-type="getUserInfo" bind:getuserinfo="onGetUserInfo">授权登录</button> | |
<!-- 登录后使用open-data --> | |
<view class="avatar-container avatar-position"> | |
<image src="{{userInfo.avatarUrl}}" wx:if="{{hasUserInfo}}" class="avatar" /> | |
<open-data wx:if="{{hasUserInfo}}" type="userNickName"></open-data> | |
</view> | |
</view> |
3 me.wxss
无
4 me.json
{ | |
} | |
5 me.js
// pages/me/me.js | |
Page({ | |
/** | |
* 页面的初始数据 | |
*/ data: { | |
hasUserInfo: false, | |
userInfo: null | |
}, | |
onLoad: function() { | |
// 页面加载时使用用户授权逻辑,弹出确认的框 | |
this.userAuthorized() | |
}, | |
userAuthorized() { | |
wx.getSetting({ | |
success: data => { | |
if (data.authSetting['scope.userInfo']) { | |
wx.getUserInfo({ | |
success: data => { | |
this.setData({ | |
hasUserInfo: true, | |
userInfo: data.userInfo | |
}) | |
} | |
}) | |
} else { | |
this.setData({ | |
hasUserInfo: false | |
}) | |
} | |
} | |
}) | |
}, | |
onGetUserInfo(e) { | |
const userInfo = e.detail.userInfo | |
if (userInfo) { | |
//. 小程序通过wx.login()获取code | |
wx.login({ | |
success: function(login_res) { | |
//获取用户信息 | |
wx.getUserInfo({ | |
success: function(info_res) { | |
//. 小程序通过wx.request()发送code到开发者服务器 | |
wx.request({ | |
url: '#;, | |
method: 'POST', | |
header: { | |
'content-type': 'application/json' | |
}, | |
data: { | |
code: login_res.code, //临时登录凭证 | |
rawData: info_res.rawData, //用户非敏感信息 | |
signature: info_res.signature, //签名 | |
encrypteData: info_res.encryptedData, //用户敏感信息 | |
iv: info_res.iv //解密算法的向量 | |
}, | |
success: function(res) { | |
if (res.data.status == ) { | |
//.小程序存储skey(自定义登录状态)到本地 | |
wx.setStorageSync('userInfo', userInfo); | |
wx.setStorageSync('skey', res.data.data); | |
} else{ | |
console.log('服务器异常'); | |
} | |
}, | |
fail: function(error) { | |
//调用服务端登录接口失败 | |
console.log(error); | |
} | |
}) | |
} | |
}) | |
} | |
}) | |
this.setData({ | |
hasUserInfo: true, | |
userInfo: userInfo | |
}) | |
} | |
} | |
}) | |
6 app.json
设置app.json的pages | |
{ | |
"pages":[ | |
"pages/me/me" | |
], | |
"window":{ | |
"backgroundTextStyle":"light", | |
"navigationBarBackgroundColor": "#fff", | |
"navigationBarTitleText": "WeChat", | |
"navigationBarTextStyle":"black" | |
}, | |
"debug":true | |
} |
测试
启动开发者服务器,启动SpringBoot的main方法。
打开微信小程序开发者工具
清空缓存
点击授权登录,并允许。
授权登录
登录成功
登录成功
查看数据库,openid、skey以及用户信息等存入了数据库。
用户信息入库
同时微信小程序将skey等存储到本地,每次发起请求时都可以携带上。
skey存储本地