nodejs使用express-session做登录状态保持

nodejs使用express-session中间件

关于req.session无法获取,跨页面登录状态保存的总结

  1. 前端使用jQuery的ajax
  2. 后端使用nodejs和express框架
  3. 跨域使用了cors
  4. 数据库用mysql
  5. 关键点:做跨域处理的时候,不但要做到能接口通讯,还要注意令浏览器自动保存登录返回的set-cookie,这样下次访问其他页面才会自动带上cookie.

排查错误的过程:

  1. req.session为undefined,无法判断之前保存在session的isLogin:网上查阅,包括我自己也出现这个情况,后来我发现一个诡异地方:就是登录成功后,后台已经返回set-cookie,为啥我下次请求不带上这个cookie呢?经过网上查阅资料有几个原因会导致:1 cookie过期,浏览器会自动筛选有效期在内的。2 cookie根本没有存在appliacation的本地上,那下次请求肯定就不带上了。于是,我发现我的响应头返回cookie时间为凌晨5点,6点是过期时间,但是接口发出时间是晚上21点,会不会是时区问题导致cookie过期了呢?于是我将cookie过期时间设置为24小时,这样就算是时区问题,依然在有效期呢。可是经过验证,依然下次请求没有带上cookie。再细心一看,原来是本地没有存到cookie,再去查资料,浏览器收到set-cookie后自动保存cookie的前提这就是最关键地方了,见第2点。
  2. 浏览器保存set-cookie的几点要素前提:首先跨域请求,是需要后台设置Access-Control-Allow-Origin,Access-Control-Allow-Credentials,credentials,同时前端也要设置xhrFields: {withCredentials: true},同时Access-Control-Allow-Origin不能为*号,要指定具体的域名。并且前端域名和后端域名是同域,换句话说,你可能解决了跨域,能获取到后台返回的数据了,但是origin不同域,浏览器依然不会自动保存cookie.所以需要满足的条件很多,总的来说就是,前后端都要配置四个字段,并且还要origin域名和后端域名一致。附图如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  3. 注销操作后,再次手动刷新首页,不能提示重新登录竟然还可以保持登录状态:经过我反复排查,竟然是注销调用接口时,前端没有带上xhrFields: {withCredentials: true}。其实道理和第二点一样。只有配置好了,注销接口才会带上这个cookie.这里可以用两种方法,一种方法 req.session.cookie.maxAge = 0,通过有效期过期给浏览器带上去,会发现点击注销的一瞬间,浏览器本地cookie就消失了(消失原因是后台响应头返回了最新的过期时间,浏览器判断已经过期就自动删除本地cookie了,所以推测如果不过期就会更新本地的cookie),此时提示重新登录。另一种方法req.session.destroy,销毁会话,也需要通过浏览器携带cookie,所以两种方法都需要配置前端配置withCredentials为true。区别只是后者,可以发现浏览器没有清空cookie,是因为后台没有返回set-cookie这个字段了但是后台自己已经销毁了cookie,所以保存在session的isLogin自然就为false。
  4. ps:登录接口,首页查询用户接口,注销接口,每次都要前端带上withCredentials也挺麻烦的,用axios,统一配置,省了粗心大意出错,避免不必要的时间成本

话不多说:nodejs代码:

const express = require('express')
const Router = require('./router')
const cors = require('cors')
const session = require('express-session')
const app = express()

app.use(express.json())
app.use(express.urlencoded({
  extended: false
}))
app.use(session({
  secret: 'lernning', //随意填写
  resave: false, //固定写法
  saveUninitialized: false,//true表示,第一次访问页面不需要登录就生成sessionId,
  cookie: {
    maxAge: 1000 * 60 * 60 * 1 , //1小时
  
  },
  rolling:true  //用户最后一次请求开始计算,重新刷新session的有效期,类似淘宝中午不吃饭一直刷,1小时不过期,如果出去午休了,回来再刷新,需要重新登录,淘宝就是不想让你吃午饭,多点为马云做贡献

}))


//配置cors中间件,解决跨域资源共享
app.use(cors({//后台启动服务器ip是127.0.0.1
  origin: 'http://127.0.0.1:5500',//这里可以解决跨域问题,但是如果要让浏览器自动保存登录成功后的set-cookie,后台必须和前端同域名,不然浏览器不会保存,除非做代理转发?(转发我没试验过)
  // origin:'http://localhost:52330',//虽然可以解决跨域,可以进行请求的接收,但是浏览器无法保存cookie,下次请求就无法带上
  credentials:true,  //告知前端可以请求头携带cookie请求,同时前端也要设置withCredentials为true
}))


app.use('/api', Router) //注册路由模块

app.listen(80, () => {
  
})

路由route.js:

const express = require('express')
const Router = express.Router() //创建路由实例
const mysql = require('mysql')

const db = mysql.createPool({
  host: '127.0.0.1', //数据库的IP地址
  user: 'root', //数据库的账号
  password: '****', //数据库的密码
  database: 'my_db_01', //指定哪个数据库
  port: 3306
})



//登录
Router.post('/login', (req, res) => {
  //判断用户登录信息是否正确
  let userInfo = req.body.username + req.body.password
  let isLogin = false
  const sqlStr = 'select * from users' //当数据字段和数据库中一样
  db.query(sqlStr, (err, sqlRes) => {
    if (err) return console.log(err.message);
    for (const per of sqlRes) {

      isLogin = userInfo === (per.username + per.password) ? true : false
      if (isLogin) break
    }
    if (isLogin) {
      //下面两个操作响应头才会返回set-cookie字段,但浏览器还不会存起来。
      // 要浏览器存起来,需要后端和前端共同设置跨域资源共享cors,并且同域名
      req.session.user = req.body //将用户的信息储存到session中
      req.session.isLogin = true //储存用户的登录状态
      console.log('登录', req.session);
      res.send({
        code: 200,
        msg: '登录成功',
        data: req.body.username
      })
    } else {
      res.send({
        code: -1,
        msg: '登录失败'
      })
    }
  })
})

//注销
Router.post('/logout', (req, res) => {  
  //方法1:清空当前客户端对应的
  // req.session.cookie.maxAge = 1000
  // res.send({
  //   code: 200,
  //   msg: '注销成功'
  // })

//方法2:express-session包的destory方法
  req.session.destroy((err) => {
    if (err) {
      res.send('注销失败')
      return 
    }

    res.send({
      code: 200,
      msg: '注销成功'
    })

  })


})


//查询用户
Router.get('/searchUser', (req, res) => {

  console.log('查询', req.session);
  if (!req.session.isLogin) {
    res.send({
      code: -1,
      msg: '你尚未登录,请先登录'
    })
    return
  }
  res.send({
    code: 200,
    data: req.session.user.username,
    msg: '查询用户信息成功'
  })
})




module.exports = Router


前端代码:login.html:

<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../jquery.js"></script>
 
</head>

<body>
  <form action="" id="loginForm">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <button type="submit">登录</button>
  </form>



  <script>
    $(document).ready(function () {//在DOM元素被加载完成的情况下执行
      $('#loginForm').on("submit", (ev) => {
        //阻止浏览器跳转     
        ev.preventDefault();
        // 自己发请求
        let loginPrams = {}
        const formData = new FormData($('#loginForm')[0]);
        formData.forEach((val, key) => {
          // console.log(val,key)  val-值,key-表单的name
          loginPrams[key] = val
        })
        $.ajax({
          type: 'POST',
          url: 'http://127.0.0.1/api/login',
          data: loginPrams,
          xhrFields: {
            withCredentials: true  //必须和后端配合设置origin,credentials,且origin同域才能浏览器自动保存
          },
          success: function (res) {
            // console.log(res)
            if (res.code === 200 && res.msg === '登录成功') {
              alert('欢迎你,' + res.data)
              window.location.href = './index.html';
            }
            if(res.code===-1){
              alert('登录失败')
            }
          }
        })
      })
    });

  </script>
</body>

</html>


首页:index.html:

<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../jquery.js"></script>
</head>

<body>
  <h1>首页</h1>
  <button id="getBtn">退出登录</button>


  <script>


    $(document).ready(function () {
      $.ajax({
        type: 'get',
        url: "http://127.0.0.1/api/searchUser",
        xhrFields: {
          withCredentials: true
        },
        success(res) {

          if (res.code === -1) {
            alert('请先登录')
            window.location.href = './login.html';
          }
        }

      })
      //登出
      $('#getBtn').on('click', function () {
        $.ajax({
          url: 'http://127.0.0.1/api/logout',
          type: 'post',
          xhrFields: {
            withCredentials: true
          },
          success(res) {
            // window.location.href='./login.html';
          }
        })
      })
    })
    this
  </script>
</body>

</html>


本文章(和掘金作者为同一人):express-session登录状态保持

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个基于 Nodejs、Express 和 EJS 的分页查询实现示例: 首先,安装必要的依赖: ``` npm install express ejs mongoose express-flash express-session ``` 然后,创建一个名为 `app.js` 的文件,引入所需模块: ```javascript const express = require('express'); const mongoose = require('mongoose'); const session = require('express-session'); const flash = require('express-flash'); const bodyParser = require('body-parser'); const app = express(); const port = process.env.PORT || 3000; // 连接 MongoDB 数据库 mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true}); // 定义数据库模型 const Article = mongoose.model('Article', { title: String, content: String, created_at: Date }); // 设置模板引擎和静态文件目录 app.set('view engine', 'ejs'); app.use(express.static(__dirname + '/public')); // 设置 session 和 flash app.use(session({ secret: 'mysecretkey', resave: true, saveUninitialized: true })); app.use(flash()); // 设置 bodyParser app.use(bodyParser.urlencoded({ extended: true })); // 定义路由 app.get('/', async function(req, res) { const perPage = 5; const page = req.query.page || 1; const articles = await Article.find() .skip((perPage * page) - perPage) .limit(perPage); const count = await Article.countDocuments(); const totalPages = Math.ceil(count / perPage); res.render('index', { articles: articles, current: page, pages: totalPages }); }); app.listen(port, function() { console.log('Server listening on port ' + port); }); ``` 在 `views` 文件夹中创建一个名为 `index.ejs` 的文件,用于显示分页数据: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>分页查询</title> </head> <body> <h1>文章列表</h1> <% if (articles.length > 0) { %> <ul> <% articles.forEach(function(article) { %> <li><%= article.title %></li> <% }); %> </ul> <% } else { %> <p>没有文章。</p> <% } %> <% if (pages > 1) { %> <div class="pagination"> <ul> <% if (current == 1) { %> <li class="disabled"><span>«</span></li> <% } else { %> <li><a href="?page=<%= current - 1 %>">«</a></li> <% } %> <% for (var i = 1; i <= pages; i++) { %> <% if (i == current) { %> <li class="active"><span><%= i %></span></li> <% } else { %> <li><a href="?page=<%= i %>"><%= i %></a></li> <% } %> <% } %> <% if (current == pages) { %> <li class="disabled"><span>»</span></li> <% } else { %> <li><a href="?page=<%= current + 1 %>">»</a></li> <% } %> </ul> </div> <% } %> </body> </html> ``` 在上面的示例中,我们使用了 `mongoose` 连接 MongoDB 数据库,并定义了一个名为 `Article` 的数据库模型。在路由中,我们使用 `await` 关键字来等待查询结果,并通过 `skip()` 和 `limit()` 方法来实现分页查询。在视图中,我们使用 EJS 模板引擎来生成分页链接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值