史上最详细微信小程序授权登录与后端SprIngBoot交互操作说明,附源代码,有疑惑大家可以直接留言,蟹蟹 2021.11.29完善更新小程序代码,
2021.11.29 更新文章
你好,我是博主宁在春,一起学习吧!!!
写这篇文章的原因,主要是因为最近在写毕业设计,用到了小程序,这中间曲曲折折,一言难尽啊。毕业设计真的让人麻脑阔😂。唉
最近在持续更新,每天推送完代码,遇到的问题都记下来,希望对大家也能有所帮助。
在网上找了很多很多,看了不下几十篇,说实话,有些给出了核心代码,添上一个微信官方的那张流程图就结束了,会的人一下就懂了。但是说实话,真的不适合入门学者,浪费很多时间都不一定能解决问题,将代码复制完不是少这就是少那,或者就是不齐,不然就是跑不起来,不知道看到这篇文章的你有没有遇到过这样的问题。
所以我自己将踩坑的经历写下来了,希望能够帮助到大家,开源进步,交流进步,一起学习!!!
注意
挺多小伙伴遇到过这个问题,如果大家对文章内容存有疑惑或者实现不了这个小demo亦或者文章中有什么错误,可以直接评论、留言或可以直接发问题到 邮箱:nzc_wyh@163.com
希望能够帮助到大家(当然,如果我可以做到的话 🦮)
看到都会尽快回复大家,谢谢大家,一起努力
微信官方文档
一、微信小程序官方登录流程图
个人理解:
调用wx.login() 获取code,这个code的作用是实现微信临时登录的url中的一个非常重要的参数。
- 微信授权的url="https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code"
- js_code所用到的值就是 获取到的code。
把获取到的code传给我们自己的SpringBoot后端,由我们后端向微信接口服务发送请求。
-
appid:应用ID,secret:应用密钥,js_code:前台传给我们的code
-
secret获取方式:
- 进入微信公众平台
- 左侧菜单选择【开发管理】
- 右侧tab选择【开发设置】
- AppSecret栏右侧点击重置会弹出一个二维码,需要开发者扫描二维码才可以重置AppSecret。出现AppSecret后点击复制,并保存你的AppSecret。
- 没保存就只能重新生成了。
后端发送请求后获取到的返回信息:
{"session_key":"G59Evf\/Em54X6WsFsrpA1g==","openid":"o2ttv5L2yufc4-VoSPhTyUnToY60"}按照官方文档所讲:自定义登录态与openid和session_key关联,有很多方式可以实现的,如:
- 第一种方式:我们可以将openid和session_key存进redis中,前端来访问的时候带上就能够访问了。
- 第二种方式:利用jwt方式生成Token返回给前端,让前端下次请求时能够带上,就能允许他们访问了。
前端将token存入storage
前端在wx.request()发起业务请求携带自定义登录态,后端进行请求头的检查就可以了。
后端返回业务数据
上述就是官方的方式,但是在现在的时代,数据是非常重要的,不可能说不将用户数据持久化的,所以这个流程会稍稍多一些操作的。
二、个人实现登录流程图
三、小程序端
先说一下,这里只是测试的Demo,是分开测试的,先在前端把我要测试的数据获取出来。
我本地没有微信的编程环境,我是拿小伙伴的微信环境进行测试的。
2.1、调用wx.login()
wx.login({success:function(res){if(res.code){console.log(res.code);}} })就是这样的一个字符串:
我们将这个返回的code,先保存起来,稍后我们在后端测试中会用上的。
2.2、调用getUserInfo()
<button open-type="getUserInfo" bindgetuserinfo="userInfoHandler"> Click me <tton> // 微信授权 wx.getUserInfo({success: function(res) {console.log(res);} })打印出来是这样的一些数据。
我们需要保存的是
至此,我们需要在前台获取的数据,已经结束了,接下来就用我们获取到的数据一起来看后端吧!!!
2021.11.29 更新
import { request } from "../../request/index.js" Page({data: {encryptedData: "",iv: "",sessionId:""},onLoad: function (options) {},getUserProfile(e) {const that = this;// 获得 encryptedData & ivwx.getUserProfile({desc: '业务需要',success: res => {this.setData({ encryptedData: res.encryptedData, iv: res.iv })// 获得 sessionIdwx.login({success: async (res) =>{const code = res.code;if(res.code){const res = await request({ url: '/weixin/sessionId/' + code })if( res.statusCode == 200 ){that.setData({ sessionId: res.data.data })const {encryptedData, iv, sessionId} = this.data;// 带着 encryptedData, iv, sessionId 去获得 tokenconst res2 = await request({ url: '/weixin/authLogin',method: "POST",data: {"encryptedData": encryptedData,"iv": iv,"sessionId": sessionId}})if( res2.data.code == 200 ){const userInfo = res2.data.data;const token = res2.data.data.token;wx.setStorageSync('userInfo', userInfo);wx.setStorageSync('token', token);wx.navigateBack({delta: 1,});}else{console.log(res2.errMsg);wx.showToast({title: '登录失败!',icon: 'error',})}}else{console.log(res.data.message);wx.showToast({title: '登录失败!',icon: 'error',})return;}}else{console.log("获取用户登录状态失败!" + res.errMsg);}}})}})},handleCancel(){wx.navigateBack({delta: 1,});} })四、SpringBoot后端
为了将代码精简,我这边只是把获取到的数据输出出来,并未真实的保存到数据中。业务操作用注释在文中展示。
项目结构:
3.1、相关jar
创建一个SpringBoot项目,或者maven项目都可以。
<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.5.2</version><relativePath/></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency><!--使用hutool中对http封装工具类 调用 HTTP 请求--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.6.5</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>3.2、yml配置文件
server:port: 8081 spring:application:name: springboot-weixinredis:database: 0port: 6379host: localhostpassword: weixin:appid: 'appid'secret: '应用密钥'3.3、公共类
就是一常量
public class RedisKey {public static final String WX_SESSION_ID = "wx_session_id"; } /*** 统一响应结果集* @author crush*/ @Data public class Result<T> {//操作代码Integer code;//提示信息String message;//结果数据T data;public Result() {}public Result(ResultCode resultCode) {this.code = resultCode.code();this.message = resultCode.message();}public Result(ResultCode resultCode, T data) {this.code = resultCode.code();this.message = resultCode.message();this.data = data;}public Result(String message) {this.message = message;}public static Result SUCCESS() {return new Result(ResultCode.SUCCESS);}public static <T> Result SUCCESS(T data) {return new Result(ResultCode.SUCCESS, data);}public static Result FAIL() {return new Result(ResultCode.FAIL);}public static Result FAIL(String message) {return new Result(message);} } /*** 通用响应状态*/ public enum ResultCode {/* 成功状态码 */SUCCESS(0, "操作成功!"),/* 错误状态码 */FAIL(-1, "操作失败!"),/* 参数错误:10001-19999 */PARAM_IS_INVALID(10001, "参数无效"),PARAM_IS_BLANK(10002, "参数为空"),PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),PARAM_NOT_COMPLETE(10004, "参数缺失"),/* 用户错误:20001-29999*/USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),USER_NOT_EXIST(20004, "用户不存在"),USER_HAS_EXISTED(20005, "用户已存在"),/* 系统错误:40001-49999 */FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"),FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"),/* 数据错误:50001-599999 */RESULT_DATA_NONE(50001, "数据未找到"),DATA_IS_WRONG(50002, "数据有误"),DATA_ALREADY_EXISTED(50003, "数据已存在"),AUTH_CODE_ERROR(50004, "验证码错误"),/* 权限错误:70001-79999 */PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陆系统!"),PERMISSION_UNAUTHORISE(70002, "权限不足,无权操作!"),PERMISSION_EXPIRE(70003, "登录状态过期!"),PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),PERMISSION_LIMIT(70005, "访问次数受限制"),PERMISSION_TOKEN_INVALID(70006, "无效token"),PERMISSION_SIGNATURE_ERROR(70007, "签名失败"),//操作代码int code;//提示信息String message;ResultCode(int code, String message) {this.code = code;this.message = message;}public int code() {return code;}public String message() {return message;}public void setCode(int code) {this.code = code;}public void setMessage(String message) {this.message = message;} } package com.crush.mybatisplus.config;import cn.hutool.core.lang.Assert; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.StringRedisSerializer;import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration;/*** redis 配置类** @author crush*/ @EnableCaching @Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();//key hasKey的序列化redisTemplate.setKeySerializer(stringRedisSerializer);redisTemplate.setHashKeySerializer(stringRedisSerializer);redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.afterPropertiesSet();return redisTemplate;}@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();stringRedisTemplate.setConnectionFactory(redisConnectionFactory);return stringRedisTemplate;} }3.4、Controller
import com.crush.weixin.commons.Result; import com.crush.weixin.entity.WXAuth; import com.crush.weixin.service.IWeixinService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;/**** @author crush* @since 2021-09-14*/ @Slf4j @RestController @RequestMapping("/weixin") public class WeixinController {@AutowiredIWeixinService weixinService;//这个就是那个使用传code进来的接口@GetMapping("/sessionId/{code}")public String getSessionId(@PathVariable("code") String code){return weixinService.getSessionId(code);}@PostMapping("/authLogin")public Result authLogin(@RequestBody WXAuth wxAuth) {Result result = weixinService.authLogin(wxAuth);log.info("{}",result);return result;} }3.5、service层
public interface IWeixinService extends IService<Weixin> {String getSessionId(String code);Result authLogin(WXAuth wxAuth); } import cn.hutool.core.lang.UUID; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.crush.weixin.commons.RedisKey; import com.crush.weixin.commons.Result; import com.crush.weixin.entity.WXAuth; import com.crush.weixin.entity.Weixin; import com.crush.weixin.entity.WxUserInfo; import com.crush.weixin.mapper.WeixinMapper; import com.crush.weixin.service.IWeixinService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service;/*** @author crush* @since 2021-09-14*/ @Slf4j @Service public class WeixinServiceImpl extends ServiceImpl<WeixinMapper, Weixin> implements IWeixinService {@Value("${weixin.appid}")private String appid;@Value("${weixin.secret}")private String secret;@AutowiredStringRedisTemplate redisTemplate;@AutowiredWxService wxService;@Overridepublic String getSessionId(String code) {String url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";String replaceUrl = url.replace("{0}", appid).replace("{1}", secret).replace("{2}", code);String res = HttpUtil.get(replaceUrl);String s = UUID.randomUUID().toString();redisTemplate.opsForValue().set(RedisKey.WX_SESSION_ID + s, res);return s;}@Overridepublic Result authLogin(WXAuth wxAuth) {try {String wxRes = wxService.wxDecrypt(wxAuth.getEncryptedData(), wxAuth.getSessionId(), wxAuth.getIv());log.info("用户信息:"+wxRes);//用户信息:{"openId":"o2ttv5L2yufc4-sVoSPhTyUnToY60","nickName":"juana","gender":2,"language":"zh_CN","city":"Changsha","province":"Hunan","country":"China","avatarUrl":"头像链接","watermark":{"timestamp":1631617387,"appid":"应用id"}}WxUserInfo wxUserInfo = JSON.parseObject(wxRes,WxUserInfo.class);// 业务操作:你可以在这里利用数据 对数据库进行查询, 如果数据库中没有这个数据,就添加进去,即实现微信账号注册// 如果是已经注册过的,就利用数据,生成jwt 返回token,实现登录状态return Result.SUCCESS(wxUserInfo);} catch (Exception e) {e.printStackTrace();}return Result.FAIL();} }2121年11月27号:应该是微信接口更新了,在此处通过解密获取到的信息中,并不包含openId啦,得自己去拿到才可以。特此在此补充,有问题大家可以一起聊
牵扯到用户信息解密的方法,想要了解,可以去微信官方文档中进行了解,我对此没有深入。
import cn.hutool.core.codec.Base64; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.crush.weixin.commons.RedisKey; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component;import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.spec.AlgorithmParameterSpec; import java.util.Random;@Slf4j @Component public class WxService {@Autowiredprivate StringRedisTemplate redisTemplate;public String wxDecrypt(String encryptedData, String sessionId, String vi) throws Exception {// 开始解密String json = redisTemplate.opsForValue().get(RedisKey.WX_SESSION_ID + sessionId);log.info("之前存储在redis中的信息:"+json);//之前存储在redis中的信息:{"session_key":"G59Evf\/Em54X6WsFsrpA1g==","openid":"o2ttv5L2yufc4-VoSPhTyUnToY60"}JSONObject jsonObject = JSON.parseObject(json);String sessionKey = (String) jsonObject.get("session_key");byte[] encData = cn.hutool.core.codec.Base64.decode(encryptedData);byte[] iv = cn.hutool.core.codec.Base64.decode(vi);byte[] key = Base64.decode(sessionKey);AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");SecretKeySpec keySpec = new SecretKeySpec(key, "AES");cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);return new String(cipher.doFinal(encData), "UTF-8");} }最后写个启动类就可以开始测试了。
@SpringBootApplication public class SpringBootWeixin {public static void main(String[] args) {SpringApplication.run(SpringBootWeixin.class);} }五、测试
写完后端,接下来,可以利用我们之前收集的那些小程序中获取到的数据啦。
1、先发送第一个请求:
code:就是之前我们获取到的数据。
http://localhost:8081/weixin/sessionId/{code}会返回一个sessionId回来,在第二个请求中需要携带。
2、再发送第二个请求
http://localhost:8081/weixin/authLogin请求方式:post
data:json格式数据
{"encryptedData":"sYiwcAM73Ci2EB3y9+C6.....","iv": "xZGOj6RwaOS==","sessionId":"我们上一个请求获取到sessionId" }请求成功是下面这样的。
我们把我们需要的存储到数据库持久化即可啦。
六、自言自语
这只是一个小demo,在使用中大都会结合security安全框架和Jwt一起使用,周末吧,周末比较有空,有空就会更新出来。
你好,我是博主宁在春,有问题可以留言评论或者私信我,大家一起交流学习!
不过都看到这里啦,点个赞吧👩💻
源码:
SpringBoot-weixin-gitee
SpringBoot-weixin-github
补充
如果拿github查看项目的话,可以在google安装一个扩展程序。看代码会更加的方便
安装之后,在github上查看项目,会在左侧多一个下面这样的目录结构。比较方便
今天的文章就到了这里啦,下次再见!!!
总结
以上是生活随笔为你收集整理的史上最详细微信小程序授权登录与后端SprIngBoot交互操作说明,附源代码,有疑惑大家可以直接留言,蟹蟹 2021.11.29完善更新小程序代码,的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: JUC系列(八)| 读写锁-ReadWr
- 下一篇: Idea中内置Translation插件