微信授權(quán)小程序的完整步驟實例
1. 登錄流程概述:
- 前端通過 wx.login() 獲取臨時登錄憑證 code
- 將 code 發(fā)送到后端服務(wù)器
- 后端通過 code 換取用戶的 openid 和 session_key
- 后端生成 JWT token 返回給前端
- 前端存儲 token 用于后續(xù)請求的身份驗證
2. 前端登錄實現(xiàn):
wx.getUserProfile是微信專門用來獲取授權(quán)人信息的api,參數(shù)有2個:
1是的desc,告訴接口你拿登錄信息做什么?
2.success是拿到登錄信息以后要做什么,就是一個回調(diào)函數(shù)。
在回調(diào)函數(shù)里面做了2件事情:
1.向本地服務(wù)發(fā)送登錄請求
2.獲取到本地的token以后,緩存在本地
// 調(diào)用微信登錄
wx.login({
success: (res) => {
if (res.code) {
// 獲取用戶信息
wx.getUserProfile({
desc: '用于完善會員資料',
success: (userInfo) => {
// 發(fā)送 code 和用戶信息到后端
wx.request({
url: 'api/user/login',
method: 'POST',
data: {
code: res.code,
userInfo: userInfo.userInfo
},
success: (loginRes) => {
// 保存 token
wx.setStorageSync('token', loginRes.data.token)
}
})
}
})
}
}
})
代碼拆開如下所示:
const { code } = await new Promise((resolve, reject) => {
wx.login({
success: resolve,
fail: reject
});
});
// 2. 獲取用戶信息
const userProfileRes = await new Promise((resolve, reject) => {
wx.getUserProfile({
desc: '用于完善會員資料',
success: resolve,
fail: reject
});
});
微信返回的數(shù)據(jù)如圖所示:
{
userInfo: {
nickName: "用戶昵稱",
avatarUrl: "頭像URL",
gender: 1, // 性別 0:未知、1:男、2:女
country: "國家",
province: "省份",
city: "城市",
language: "zh_CN" // 語言
},
rawData: "原始數(shù)據(jù)字符串",
signature: "簽名",
encryptedData: "加密數(shù)據(jù)",
iv: "加密算法的初始向量"
}
wx.login會返回一個臨時的code,這個 code 不能直接用來識別用戶, 后端需要用這個 code 去微信服務(wù)器換取 openid 和 session_key, openid 才是用戶的真實唯一標(biāo)識。
也就是說在前端你用wx.login和wx.getUserProfile獲取到的信息,都是用戶公開信息,不能用來做用戶的唯一標(biāo)識,真正的唯一標(biāo)識是openid,還是得有小程序的后端代碼獲取。
3. 后端處理流程:
從代碼中可以看到,后端的登錄處理主要包含以下步驟:
在前端wx.login里面你拿到了用戶的臨時code,然后你通過本地后臺的'/user/login接口傳給后端,后端拿到你的code以后就會調(diào)用微信服務(wù),通過微信服務(wù)拿到sessionInfo里面的openid。
在你的數(shù)據(jù)庫User表里面一定有一個字段是openid,用來搜索用戶信息的。如果User里面有這個openId就說明它登錄了,沒有那就要后端把這個用戶及它的openid存到數(shù)據(jù)庫里面去。(用戶信息你可以通過前端和code一起傳過來)
// 調(diào)用微信登錄服務(wù)
const wechatService = new (require('../services/wechat'))();
const sessionInfo = await wechatService.code2Session(code);
if (!sessionInfo || !sessionInfo.openid) {
throw new Error('獲取微信用戶信息失敗');
}
console.log('微信登錄成功,openId:', sessionInfo.openid);
// 查找或創(chuàng)建用戶
let user = await User.findOne({ where: { openId: sessionInfo.openid } });
4. 身份認(rèn)證機制:
微信用戶信息創(chuàng)建更新完以后,我們需要獲取token,這個件事也是在后端登錄接口里面做的。
4.1.后端獲取token
一般我們通過第三方庫拿到j(luò)wt實例,然后再去認(rèn)證token(身份令牌),比如在node服務(wù)里面,我們用的是jsonwebtoken
const jwt = require('jsonwebtoken');
- 使用 JWT(JSON Web Token)進(jìn)行身份驗證:
// 生成JWT token
const token = jwt.sign(
{
userId: user.userId, // 用戶ID
openId: user.openId // 微信openId
},
// 第二個參數(shù):密鑰
process.env.JWT_SECRET || 'your_jwt_secret_key',
// 第三個參數(shù):配置選項,7d表示: token有效期7天
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
);
在開發(fā)環(huán)境里面,JWT_SECRET一般是放在.env文件里面,就是一個向以下格式的字符串,我一般會執(zhí)行一個命令
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
利用node自帶的crypto模塊幫我生成。
JWT格式
// JWT格式:header.payload.signature // 例如: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // eyJ1c2VySWQiOjEsIm9wZW5JZCI6Im94eHh4IiwiaWF0IjoxNTE2MjM5MDIyfQ. // SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
你只要設(shè)置了,jwt.sign()就可以根據(jù)JWT_SECRET創(chuàng)建出一個token出來。
那在正式環(huán)境里面JWT_SECRET怎么設(shè)置呢?
運維配置: 1.部署時通過運維工具(如 Docker、K8s、PM2)設(shè)置環(huán)境變量: docker run -e JWT_SECRET=your_secure_key your-app 2.在代碼中讀取環(huán)境變量: const token = jwt.sign(payload, process.env.JWT_SECRET); 用process.env.JWT_SECRET既可以拿到env里面配置信息,也能拿到運維配置信息
在線上環(huán)境里面,你想要安全系數(shù)更高,就3到6個月?lián)Q一次JWT_SECRET就好了。不管是線上,還是開發(fā)環(huán)境,我們都可以用node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"拿到一個唯一的JWT_SECRET。
小程序后端的login請求結(jié)束以后會把token返回出去
ctx.body = {
code: 200,
message: '登錄成功',
data: {
token,
userInfo: {
userId: user.userId,
nickName: user.nickName,
avatarUrl: user.avatarUrl,
phoneNumber: user.phoneNumber,
openId: user.openId,
balance: user.balance || 0,
status: user.status,
createTime: user.createTime,
lastLoginTime: user.lastLoginTime
}
}
};4.2前端拿到token以后需要做的事情
前端拿到token要做三件事,1.緩存token,2.發(fā)送請求的時候把token帶上,3.后端api驗證token
4.2.1緩存token
在前端里面我們用const app = getApp();拿到小程序的全局變量,這個getApp都做了什么事情
// app.js
App({
globalData: {
userInfo: null, // 用戶信息
hasUserInfo: false, // 是否已登錄
token: null, // 登錄token
baseUrl: 'http://localhost:3000/api', // API基礎(chǔ)地址
selectedCategoryId: null // 選中的分類ID
},
// 檢查登錄狀態(tài)
checkLoginStatus() {
try {
const token = wx.getStorageSync('token');
const userInfo = wx.getStorageSync('userInfo');
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
this.globalData.hasUserInfo = true;
return true;
}
return false;
} catch (error) {
return false;
}
},
// 更新登錄狀態(tài)
updateLoginStatus(token, userInfo) {
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
this.globalData.hasUserInfo = true;
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', userInfo);
} else {
this.globalData.token = null;
this.globalData.userInfo = null;
this.globalData.hasUserInfo = false;
wx.removeStorageSync('token');
wx.removeStorageSync('userInfo');
}
}
})
最主要的就是
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', userInfo);
使用的時候就這樣:
const app = getApp();
// 1. 獲取全局?jǐn)?shù)據(jù)
const { userInfo, token } = app.globalData;
// 2. 調(diào)用全局方法
app.checkLoginStatus();
app.updateLoginStatus(token, userInfo);
在代碼里面一般這樣用,在進(jìn)入獲取用戶信息的時候用app.checkLoginStatus(),在登錄的時候用:app.updateLoginStatus(token, completeUserInfo);
// 獲取用戶信息時
getUserInfo() {
// 從全局狀態(tài)獲取登錄信息
const { hasUserInfo, userInfo } = app.globalData;
if (hasUserInfo && userInfo) {
// 使用全局?jǐn)?shù)據(jù)更新頁面
this.setData({
userInfo,
hasUserInfo: true
});
} else {
// 檢查登錄狀態(tài)
const isLoggedIn = app.checkLoginStatus();
// ...
}
}
// 登錄成功時
// 6. 更新全局狀態(tài)
app.updateLoginStatus(token, completeUserInfo);
4.2.2請求帶上token
緩存本地token能干嘛?就是在請求后端接口的時候,從緩存里面拿到token,然后放到請求里面去
const token = wx.getStorageSync('token');
wx.request({
url: 'api/xxx',
header: {
'Authorization': `Bearer ${token}`
}
})
一般情況下,我們會在小程序項目里面有個請求攔截文件,進(jìn)行統(tǒng)一處理,所以你在請求api的時候一般看不到,如下:

4.2.3后端解析token,驗證身份
當(dāng)后端接收到請求以后,它會用jwt實例去解析token,token其實就是userId和openid的加密字符串。
// 當(dāng)收到請求時,后端會:
try {
// 1. 從請求頭獲取 token
const token = ctx.header.authorization?.split(' ')[1];
// 2. 使用相同的密鑰驗證 token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 3. 如果驗證成功,可以獲取到用戶信息
const userId = decoded.userId;
const openId = decoded.openId;
// 4. 驗證失敗會拋出異常
} catch (err) {
// token 無效或已過期
ctx.throw(401, '無效的token');
}
如果你想知道token里面到底是什么就用免費的JWT解析器解析一下看看

4.2.4.退出登錄處理
前端微信刪除token,然后后端會更新用戶登錄狀態(tài)
// 前端清除本地存儲的token
wx.removeStorageSync('token')
// 后端更新用戶狀態(tài)
await User.update({
updateTime: new Date()
}, {
where: { userId }
});
5.利用心跳包來維護(hù)登錄狀態(tài)
5.1概念
心跳包就是前端有個接口,定時的向后端發(fā)送請求,讓服務(wù)器告訴我,我還活著沒有
// 心跳包就像人的心跳一樣,定期發(fā)送一個信號,表示"我還活著"
// 例如:每30秒發(fā)送一次請求
setInterval(() => {
wx.request({
url: '/api/heartbeat',
method: 'POST',
success: () => {
console.log('心跳包發(fā)送成功');
}
});
}, 30000); // 30秒
具體的實現(xiàn)如下:
前端
// 在小程序中實現(xiàn)心跳機制
const startHeartbeat = () => {
// 1. 首次連接
sendHeartbeat();
// 2. 定期發(fā)送
const intervalId = setInterval(() => {
sendHeartbeat();
}, 30000);
// 3. 保存定時器ID,以便可以停止
wx.setStorageSync('heartbeatInterval', intervalId);
};
const sendHeartbeat = async () => {
try {
const res = await wx.request({
url: 'api/user/heartbeat',
method: 'POST',
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
}
});
if (res.statusCode === 200) {
console.log('心跳成功');
} else {
console.log('心跳失敗,可能需要重新登錄');
}
} catch (error) {
console.error('心跳請求失敗:', error);
}
};
// 在用戶登錄時啟動心跳
onLogin() {
startHeartbeat();
}
// 在用戶退出時停止心跳
onLogout() {
const intervalId = wx.getStorageSync('heartbeatInterval');
if (intervalId) {
clearInterval(intervalId);
}
}
后端
// 在Redis中記錄用戶在線狀態(tài)
class UserController {
static async heartbeat(ctx) {
const userId = ctx.state.user.userId;
// 使用Redis記錄用戶在線狀態(tài)
await redis.setex(
`user:online:${userId}`, // key
60, // 過期時間(秒)
'online' // value
);
ctx.body = {
code: 200,
message: 'Heartbeat received'
};
}
// 檢查用戶是否在線
static async checkUserOnline(userId) {
const status = await redis.get(`user:online:${userId}`);
return status === 'online';
}
}
這個機制的好處是:
- 實時性:可以及時發(fā)現(xiàn)用戶離線
- 可靠性:通過定期檢查確保連接狀態(tài)
- 靈活性:可以根據(jù)需求調(diào)整心跳頻率
- 安全性:可以及時清理過期的會話
缺點是:
- 需要額外的服務(wù)器資源
- 增加了網(wǎng)絡(luò)流量
- 需要考慮移動設(shè)備的電量消耗
- 需要處理網(wǎng)絡(luò)波動的情況
您覺得需要在項目中添加這個功能嗎?我可以幫您實現(xiàn)。
這個登錄認(rèn)證流程的優(yōu)點是:
- 使用微信官方的登錄機制,安全可靠
- 采用 JWT 進(jìn)行身份驗證,無需保存會話狀態(tài)
- token 有過期機制,增加安全性
- 支持自動創(chuàng)建新用戶和更新用戶信息
- 完整的錯誤處理和日志記錄
總結(jié)
到此這篇關(guān)于微信授權(quán)小程序完整步驟的文章就介紹到這了,更多相關(guān)微信授權(quán)小程序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序 this.triggerEvent()的具體使用
這篇文章主要介紹了微信小程序 this.triggerEvent()的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
鍵盤上一張下一張兼容IE/google/firefox等瀏覽器
鍵盤上一張下一張的效果想必大家都有見到過吧,本文為大家介紹的這個兼容IE,google,firefox等主流瀏覽器2014-01-01
javascript學(xué)習(xí)總結(jié)之js使用技巧
本篇文章給大家分享javascript學(xué)習(xí)總結(jié)之js使用技巧,都是精華喔~小伙伴快來學(xué)習(xí)吧。2015-09-09
微信小程序?qū)崿F(xiàn)滑動切換自定義頁碼的方法分析
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)滑動切換自定義頁碼的方法,結(jié)合實例形式分析了微信小程序頁碼動態(tài)切換相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下2018-12-12

