【nodejs学习笔记】用户登录相关:cookie、session 和 redis的使用和优缺点

cookie

什么是 cookie:

  • 存储在浏览器的一段字符串,最大 5kb
  • 跨域不共享
  • 格式形如:key1=value1; key2=value2
  • 每次发送 http 请求,会将请求域的 cookie 一起发送给 server

客户端查看、修改 cookie

开发者工具查看:
在这里插入图片描述
domain: cookie生效的域名;
可以直接这样追加cookie:
在这里插入图片描述
注意:每对 key=value 之间,是用“; ”分隔的,分号后面还有空格

server 端操作 cookie

获取-示例

const serverHandle = (req, res) => {
    
    
    // 解析 cookie
    req.cookie = {
    
    }
    const cookieStr = req.headers.cookie || '' 
    console.log(cookieStr)  // 格式:key1=value1; key2=value2
    cookieStr.split(';').forEach(item => {
    
    
        if(!item) return
        const [key, val] = item.split('=')
        req.cookie[key.trim()] = val.trim() // 用 trim 去掉首尾空格
    })
}
const server = http.createServer(serverHandle)
server.listen(8000)

写入-示例

// 设置 cookie 过期时间的方法
const getCookieExpires = () => {
    
    
    const d = new Date()
    d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
    return d.toGMTString()
}
const handleUserRouter = (req, res) => {
    
    
    const method = req.method
    // 模拟一下登录
    if(method === 'GET' && req.path === '/api/user/login'){
    
    
        const {
    
     username, password } = req.query
        const result = login(username, password)  // login 方法是去数据库查询对应的用户数据并返回
        return result.then(data => {
    
    
            if(data.username) {
    
    
               // 设置 cookie
               res.setHeader('Set-Cookie', `username=${
      
      data.username};path=/;httpOnly;expires=${
      
      getCookieExpires()}`)
               return {
    
     message: '狗狗提示:登录成功', errno: 1}
            }
            return {
    
     message: '狗狗提示:登录失败', errno: -1 }
        })
    }
}

好吧,写了这么多,其实不用关注上述具体的代码,主要关注这句就行了:

res.setHeader('Set-Cookie', `username=${
      
      data.username};path=/;httpOnly;expires=${
      
      getCookieExpires()}`)

path=/ 表示对所有路由都生效,如果不加的话,只会对当前 api 的路由生效。比如上述代码:如果不设置path=/ ,那么写入的 cookie 只对/api/user/login有效。
expires: 设置 cookie 过期时间。
http-Only: 加了这个设置,表示该 cookie 只允许后端修改,不允许前端修改,可以避免前端对该 cookie 进行修改。

测试一下:
在这里插入图片描述
可以看到,响应头里多了这个:
在这里插入图片描述
可以在 application 看下 cookie 信息:
在这里插入图片描述

使用 cookie 的弊端

如果直接把用户信息存在cookie里,容易泄露用户信息,非常危险。
如何解决:cookie 只存储用户的身份标识,如用一串字符串表示的 userid;server 端通过 session 存储用户信息,然后用 userid 去查找对应的用户信息。

session

session 运行在服务器端,当客户端第一次访问服务器时,可以将用户的登录信息保存;
当用户访问其他界面时,可以通过 session 判断用户的登录状态,做出提示。

session的工作流程:

当客户端访问服务器并发送第一次请求时,服务器端会创建一个 session 对象,生成一个类似于key=value的键值对;

然后将 key 返回给客户端,存入 cookie,当客户端再次访问时,携带 key(cookie),找到对应的session(value);

用户的信息保存在 session 中。

代码示例

app.js:

const handleUserRouter = require('./src/router/user')
// session 数据
const SESSION_DATA = {
    
    }
const serverHandle = (req, res) => {
    
    
   // …… 
   // 解析 session
   let needSetCookie = false
   let userId = req.cookie.userid
   if (userId) {
    
    
        if(!SESSION_DATA[userId]){
    
    
            SESSION_DATA[userId] = {
    
    }
        }
   } else {
    
    
        needSetCookie = true
        userId = `${
      
      Date.now()}_${
      
      Math.random()}`
        SESSION_DATA[userId] = {
    
    }
   }
   req.session = SESSION_DATA[userId]
   // ……
   // 处理user路由,如果没有 cookie,set cookie
   const userResult = handleUserRouter(req, res)
   if(userResult) {
    
    
       userResult.then(userData => {
    
    
       	   // setCookie
           if(needSetCookie) {
    
    
               res.setHeader('Set-Cookie', `userid=${
      
      userId};path=/;httpOnly;expires=${
      
      getCookieExpires()}`)
           }
           res.end(JSON.stringify(userData))
       })
       return
   }
}

router/user.js:

const handleUserRouter = (req, res) => {
    
    
    const method = req.method // GET POST
    // 登录
    if (method === 'POST' && req.path === '/api/user/login') {
    
    
        const {
    
     username, password } = req.body
        const result = login(username, password) // 根据用户名和 password 去数据库查询相应的用户信息
        return result.then(data => {
    
    
            if (data.username) {
    
    
                // 设置 session
                req.session.username = data.username
                req.session.realname = data.realname
        		return {
    
     message: '狗狗提示:登录成功', errno: 1}
            }
            return {
    
     message: '狗狗提示:登录失败', errno: -1 }
        })
    }
}

使用 session 的问题

  • 目前 session 直接是 js 变量,放在 nodejs 进程内存中。
  • 带来的问题1:进程内存有限,如果访问量过大,内存暴增怎么办?(因为操作系统会限制一个进程的最大可用内存,内存是很有限的)
  • 带来的问题2:正式线上运行是多进程,进程之间,内存无法共享。

解决方案

使用 redis:

  • web server 最常用的缓存数据库,数据存放在内存中
  • 相比于 mysql,访问速度快(内存和硬盘不是一个数量级的);
  • 成本更高,可存储的数据量更小(内存的硬伤)

为何 session 适合用 redis

  • session 访问频繁, 对性能要求极高
  • session 可以不必考虑断电丢失数据的问题(内存的硬伤)
  • session 的数据量不会太大(相比于 mysql 中存储的数据)

为何网站数据不适合用 redis

  • 操作频率不是太高(相比于 session 操作)
  • 断电不能丢失,必须保留
  • 数据量太大,内存成本太高

redis

redis 是一个高性能的 key-value 数据库。
支持存储的 value 类型包括:string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
使用内存存储。

下载安装

-windows:下载安装教程
-mac:brew install redis

使用

下载安装完成后,打开终端,输入 redis-server,回车,启动 redis 服务:
在这里插入图片描述
然后另外再打开一个终端,输入 redis-cli,就可以使用测试客户端程序 redis-cli 和 redis 服务交互了:
比如setgetkeys *del……
在这里插入图片描述

应用在项目中(示例)

首先 npm i redis
代码示例(摘自 nodejs 搭建博客课程):

工具函数

conf/db.js:

    // redis
    REDIS_CONF = {
    
    
        port: 6379,
        host: '127.0.0.1'
    }

db/redis.js:

const redis = require('redis')
const {
    
     REDIS_CONF } = require('../conf/db.js')

// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)

// 连接数据库,启动之后立刻执行
!(async function () {
    
    
    await redisClient.connect()
        .then(() => console.log('redis connect success!'))
        .catch(console.error)
})()

// set
async function set(key, val) {
    
    
    let objVal
    if (typeof val === 'object') {
    
    
        objVal = JSON.stringify(val)
    } else {
    
    
        objVal = val
    }
    await redisClient.set(key, objVal)
}

// get
async function get(key) {
    
    
    try {
    
    
        let val = await redisClient.get(key)

        if (val == null) return val

        try {
    
    
            val = JSON.parse(val) // 尝试转换为 JS 对象
        } catch (err) {
    
     }

        return val
    } catch (err) {
    
    
        throw err
    }
}

module.exports = {
    
     set, get } 

把 session 存入 redis

app.js:

const serverHandle = (req, res) => {
    
    
	// ……
    // 解析 session (使用 redis)
    let needSetCookie = false
    let userId = req.cookie.userid
    if (!userId) {
    
    
        needSetCookie = true
        userId = `${
      
      Date.now()}_${
      
      Math.random()}` // 模拟一下唯一的用户 id
        // 初始化 redis 中的 session 值
        set(userId, {
    
    })
    }
    // 获取 session
    req.sessionId = userId
    get(req.sessionId).then(sessionData => {
    
    
        if (sessionData == null) {
    
    
            // 初始化 redis 中的 session 值
            set(req.sessionId, {
    
    })
            // 设置 session
            req.session = {
    
    }
        } else {
    
    
            // 设置 session
            req.session = sessionData
        }
        // 处理 post data
        return getPostData(req)
    }).then(postData => {
    
    
        req.body = postData
        // ……
    })
}

router/user.js:

const {
    
     set } = require('../db/redis')

const handleUserRouter = (req, res) => {
    
    
    const method = req.method // GET POST
    // 登录
    if (method === 'POST' && req.path === '/api/user/login') {
    
    
        const {
    
     username, password } = req.body
        const result = login(username, password) // 根据用户名和 password 去数据库查询相应的用户信息
        return result.then(data => {
    
    
            if (data.username) {
    
    
                // 设置 session
                req.session.username = data.username
                req.session.realname = data.realname
                // 同步到 redis
                set(req.sessionId, req.session)
        		return {
    
     message: '狗狗提示:登录成功', errno: 1}
            }
            return {
    
     message: '狗狗提示:登录失败', errno: -1 }
        })
    }
}

redis相关教程

菜鸟教程

总结

Q: session 如何实现登录?

  • cookie 如何实现登录校验?
  • session 和 cookie的关系?
  • session 为何需要存储在 redis 中?

Answer

客户端登录后,服务端会设置一个 cookie,把用户信息带上,然后返给前端;
客户端拿到 cookie 后,只要是在同域下,在请求接口的时候都会自动带上 cookie。
这样就实现了登录校验,其中还会设置 cookie 的过期时间、安全性等等。

一开始 cookie 是直接存了用户信息的,这样不安全,所以改存用户标识,然后用 session 将对应的用户信息存储到服务端。也就是说,cookie 只存用户身份的标识,session 存储着用户标识对应的用户信息。

因为进程有内存限制,且进程的内存是相互隔离的,存储到 redis 就可以解决这些问题。

猜你喜欢

转载自blog.csdn.net/dongkeai/article/details/127523449