根据微信官方文档(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)
      }
    })

完成以上步骤后,一个基本的小程序登录模块即实现完成。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐