SpringBoot整合微信小程序登录
共 7663字,需浏览 16分钟
·
2020-09-01 10:50
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 邓维-java
来源 | urlify.cn/aEB3Qj
微信小程序登录流程
微信小程序登录流程涉及到三个角色:小程序、开发者服务器、微信服务器
三者交互步骤如下:
第一步:小程序
通过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:
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.4.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-boot-starterartifactId> <version>3.3.2version>dependency>
wx返回的用户信息:
/**
* @Author dw
* @ClassName WeChatUserInfo
* @Description 微信用户信息
* @Date 2020/8/28 14:14
* @Version 1.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 2020/8/28 10:56
* @Version 1.0
*/
public class WeChatUtil {
public static JSONObject getSessionKeyOrOpenId(String code) {
String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
HashMap 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 Base64DecodingException {
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionKey);
// 偏移量
byte[] ivByte = Base64.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, "UTF-8");
return JSONUtil.parseObj(result);
}
} catch (Exception e) {
}
return null;
}
登录controller:
/**
* @Author dw
* @ClassName WeChatUserLoginController
* @Description
* @Date 2020/8/28 14:12
* @Version 1.0
*/
@RestController
public class WeChatUserLoginController {
@Resource
private IUserService userService;
/**
* 微信用户登录详情
*/
@PostMapping("wx/login")
public ResultInfo user_login(@RequestBody WeChatUserInfo weChatUserInfo) throws Base64DecodingException {
// 2.开发者服务器 登录凭证校验接口 appId + appSecret + 接收小程序发送的code
JSONObject SessionKeyOpenId = WeChatUtil.getSessionKeyOrOpenId(weChatUserInfo.getCode());
// 3.接收微信接口服务 获取返回的参数
String openid = SessionKeyOpenId.get("openid", String.class);
String sessionKey = SessionKeyOpenId.get("session_key", String.class);
// 用户非敏感信息:rawData
// 签名:signature
JSONObject rawDataJson = JSONUtil.parseObj(weChatUserInfo.getRawData());
// 4.校验签名 小程序发送的签名signature与服务器端生成的签名signature2 = sha1(rawData + sessionKey)
String signature2 = DigestUtils.sha1Hex(weChatUserInfo.getRawData() + sessionKey);
if (!weChatUserInfo.getSignature().equals(signature2)) {
return ResultInfo.error( "签名校验失败");
}
//encrypteData比rowData多了appid和openid
JSONObject userInfo = WeChatUtil.getUserInfo(weChatUserInfo.getEncrypteData(),
sessionKey, weChatUserInfo.getIv());
// 5.根据返回的User实体类,判断用户是否是新用户,是的话,将用户信息存到数据库;不是的话,更新最新登录时间
QueryWrapper userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.lambda().eq(User::getLoginName, openid);
int userCount = userService.count(userQueryWrapper);
// uuid生成唯一key,用于维护微信小程序用户与服务端的会话(或者生成Token)
String skey = UUID.randomUUID().toString();
if (userCount <= 0) {
// 用户信息入库
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 {
// 已存在,更新用户登录时间
}
//6. 把新的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("-1");
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>
<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) {
// 1. 小程序通过wx.login()获取code
wx.login({
success: function(login_res) {
//获取用户信息
wx.getUserInfo({
success: function(info_res) {
// 2. 小程序通过wx.request()发送code到开发者服务器
wx.request({
url: 'http://localhost:8080/wx/login',
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 == 200) {
// 7.小程序存储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存储本地
粉丝福利:108本java从入门到大神精选电子书领取
???
?长按上方锋哥微信二维码 2 秒 备注「1234」即可获取资料以及 可以进入java1234官方微信群
感谢点赞支持下哈