基于 JWT 的微信小程序登录态管理 | Taro & Node.js 实践
根据微信官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html),微信小程序的登录流程主要分为以下步骤:前端调用 wx.login() 获取临时登录凭证 code,后端再使用该 code 以及小程序的 appId 和 appSecret 调用 auth.code2Session 接口,进而获取用户的唯一标识 OpenID 和会话密钥 session_key。此后,系统可通过 OpenID 来识别用户身份。具体前后端实现逻辑如下:
获取 session_key 和 OpenID
本示例中,前端采用 Taro + React 框架,后端使用 Node.js + Express 框架。
前端首先调用 Taro.login() 获取临时登录凭证 code,随后请求后端登录接口,并将 code 发送至服务端。服务端接收 code 后,结合小程序的 appId 和 appSecret 调用微信的 auth.code2Session 接口,从而取得 session_key 和 OpenID,最后将这些信息返回给前端。关键代码示例如下:
// 前端
const wxlogRes = await Taro.login()
const logRes = await login(wxlogRes.code)
//后端
const { code } = req.body;
if (!code) {
return res.status(400).json({
statusCode: 400,
message: "缺少登录凭证code",
});
}
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.appId}&secret=${config.appSecret}&js_code=${code}&grant_type=authorization_code`;
// 调用微信 auth.code2Session 接口
const wxResponse = await fetch(url);
const wxData = await wxResponse.json();
if (wxData.errcode) {
console.error('微信 jscode2session 接口调用失败:', wxData.errmsg);
return res.status(500).json({
statusCode: 500,
message: "微信登录服务暂时不可用",
error: wxData.errmsg,
});
}
const { openid, session_key } = wxData;
成功获取用户唯一标识 OpenID 后,后端会查询该用户是否已存在于用户表中。若不存在,则将其注册为新用户;若已存在,则更新相关用户信息。随后生成登录态 Token,并将用户信息及 Token 返回前端。部分代码如下:
// 前端
const logRes = await login(wxlogRes.code)
Taro.setStorageSync('token', logRes.data.data.token)
// 后端
let [userResults] = await req.db.execute("SELECT * FROM users WHERE openid = ?", [openid]);
let user;
if (userResults.length === 0) {
const [result] = await req.db.execute(
"INSERT INTO users (openid) VALUES (?,?)",
[openid]
);
user = {
id: result.insertId,
openid
};
} else {
user = userResults[0];
}
const token = generateToken({
userId: user.id,
openid: user.openid,
});
res.json({
statusCode: 200,
data: {
token,
userInfo: {
id: user.id,
openid: user.openid,
},
},
message: "登录成功",
});
维持登录态
小程序通常通过 Token 维持登录状态。具体流程为:服务端使用 JWT 根据 OpenID 等信息生成 Token,并发送至小程序端,小程序将 Token 存储于本地 Storage 中。此后每次请求均在 HTTP 请求头中携带该 Token,服务端接收到请求后验证 Token 的有效性——验证成功则返回数据,失败则返回 401 状态码(Unauthorized)。部分实现代码如下:
// -----------后端----------------
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = "7d";
// 生成JWT token
const generateToken = (payload) => {
return jwt.sign(payload, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
issuer: "your-issuer",
audience: "your-audience",
});
};
// 验证JWT token
const verifyToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
throw new Error("Invalid token");
}
};
module.exports = {
generateToken,
verifyToken,
};
// --------------请求拦截中间件----------------
const { verifyToken } = require("../utils/jwt");
const authMiddleware = (req, res, next) => {
try {
// 从请求头获取token
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
statusCode: 401,
message: "未提供认证信息",
});
}
const token = authHeader.substring(7);
// 验证token
const decoded = verifyToken(token);
// 将用户信息添加到请求对象
req.userId = decoded.userId;
req.openid = decoded.openid;
next();
} catch (error) {
return res.status(401).json({
statusCode: 401,
message: "认证失败",
error: error.message,
});
}
};
module.exports = authMiddleware;
// ----------后端路由----------------
const loginController = require("../controllers/loginController");
const authMiddleware = require("../middleware/auth");
const router = express.Router();
router.post("/login", loginController.login);
router.use(authMiddleware);
// 这里放需要拦截的路由
// -----------------前端请求事例--------------------
export const updateUserInfo = async params => {
try {
const result = await Taro.request({
url: `${BASE_URL}/updateUserinfo`,
method: 'PUT',
header: {
Authorization: `Bearer ${Taro.getStorageSync('token')}`
},
data: params
})
if (result.statusCode === 401) {
// Token 过期或无效
Taro.removeStorageSync('token')
await showLoginDialog()
throw new Error('登录已过期')
}
return result
} catch (err) {
Taro.showToast({ title: err.errMsg, icon: 'error' })
throw err
}
}
前端唤起重新登录弹窗
当前端发起的请求返回 401 状态码时,表明 Token 已失效,需提示用户重新登录。可通过弹窗通知用户,并触发全局登录事件。在应用的入口文件中监听这一事件,事件触发时执行相应的登录操作。代码如下:
// --------入口文件---------
eventCenter.on('needLogin', () => {
// 登录相关代码
})
//--------请求拦截提示-----------
Taro.showModal({
title: '登录过期',
content: '请重新登录',
showCancel: true,
confirmText: '去登录',
cancelText: '取消',
success: res => {
isLoginDialogShowing = false
if (res.confirm) {
// 触发全局登录事件
Taro.eventCenter.trigger('needLogin')
resolve(true)
} else {
resolve(false)
}
},
fail: () => {
isLoginDialogShowing = false
resolve(false)
}
})
完成以上步骤后,一个基本的小程序登录模块即实现完成。
更多推荐

所有评论(0)