史上最详细微信小程序授权登录与后端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插件