基于cookie-session中间件跟踪用户的状态

需求背景

Jietu20211216-165116.png

设计实现

  • 安装 cookie-session 中间件
npm install cookie-session
复制代码
  • 使用 cookie-session 中间件
app.use(cookieSession({
  name: 'js_session_id',
  keys: ['rbac'],
  // maxAge: 24 * 60 * 60 * 1000 // 24 hours
}));
复制代码

解读:

  • 请求到来时,获取 cookie 中 js_session_id 的值,这个值就是真正的 session_id。
Cookie:js_session_id=eyJpc0xvZ2dlZCI6dHJ1ZX0=;
复制代码
  • 根据 session_id 获取服务器内存中获取 session 信息。
  • 根据 session 信息判断用户是否已经登录。

注释:

  • 如果请求头中没有 cookie,
  • 或者 cookie 中找不到 js_session_id 字段,
  • 或者根据 session_id (eyJpc0xvZ2dlZCI6dHJ1ZX0=) 找不到与之对应的 session

都代表没有登录。

案例说明

客户端发起请求/sciences-member,请求被中间件 checkLogin 拦截。如果已经登录,那么继续执行下面的逻辑。

router.get(
  "/sciences-member", checkLogin, async function (req: Request, res: Response) {
    ...
    res.status(200).send(formatResult(data));
  }
);
复制代码

checkLogin 中间件的逻辑很简单。判断 session 是否存在,并且 isLogged 是否为真。

function checkLogin(req: Request, res: Response, next: NextFunction) {
  if (req.session && req.session.isLogged) {
    next();
  } else {
    res.status(401).send(formatResult("CheckSessionLogin", "没有登录"));
  }
}
复制代码

isLogged 的值是在登录成功时动态添加到 session 对象上去的。

router.post("/login", function (req: Request, res: Response) {
  const username = req.body.username;
  const password = req.body.password;
  if (username === "luohao" && password === "123") {
    req.session && (req.session.isLogged = true);
    res.status(200).send(formatResult("登录成功"));
  } else {
    res.status(500).send(formatResult(null, "用户名或者密码错误"));
  }
});
复制代码

在 checkLogin 中间件执行的逻辑中,假设不满足已经登录的条件,服务器返回 401 状态码。
前端最好能针对 401 状态码统一处理一下。

instance.interceptors.response.use(
  function (response) {
    return response.data;
  },
  function (error) {
    // 重新登录
    if (error.response.status === 401) {
      return (window.location.href = "http://localhost:8088/index.html");
    }
    return Promise.reject(error.response.data);
  }
);
复制代码

注意事项

由于跨域是不可避免的。在跨域的情况下,出于安全性的考虑,XHR 不允许请求携带 cookie 信息。如果不做特殊处理,那么所有请求始终是以未登录的状态发起。为了能够在请求头中携带cookie,需要客户端和服务端协作。

  • 客户端配置
const instance = axios.create({
  ...
  withCredentials: true, // 表示跨域请求时是否需要使用凭证
  ...
});
复制代码
  • 服务端配置
res.header("Access-Control-Allow-Credentials", "true");
复制代码

参考代码

完整的案例代码