node.js博客学习之登录校验和登录信息存储

2,440 阅读1分钟

cookie

解析cookie

    req.cookie = {}
    const cookieStr = req.headers.cookie || ''  // k1=v1;k2=v2;k3=v3
    cookieStr.split(';').forEach(item => {
        if (!item) {
            return
        }
        const arr = item.split('=')
        const key = arr[0].trim()
        const val = arr[1].trim()
        req.cookie[key] = val
    })

登录验证测试

手动修改前端cookie进行测试,之后会先登录再测试

在app.js中操作cookie(服务端)

  • 登录成功则将信息存入cookie

  • 登录后把信息放入cookies
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
  • cookies限制

限制前台修改cookies httpOnly

限制过期时间 expires=${getCookieExpires()

// 获取 cookie 的过期时间
const getCookieExpires = () => {
    const d = new Date()
    d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
    console.log('d.toGMTString() is ', d.toGMTString())
    return d.toGMTString()
}

session

介绍

  • cookie会暴露username很危险

  • 如何解决:cookie中存userid,server端对应username

  • 解决方案:session,即server端存储用户信息

解析session

// // session 数据
  const SESSION_DATA = {}
// 解析 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]

app.js转发

 const userResult = handleUserRouter(req, res) //登陆路由
 if (userResult) {
            userResult.then(userData => {
                if (needSetCookie) {
                    res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
                }

                res.end(
                    JSON.stringify(userData)
                )
            })
            return
        }

登录路由控制

if (method === 'POST' && req.path === '/api/user/login') {
        const { username, password } = req.body
        // const { username, password } = req.query
        const result = login(username, password)
        return result.then(data => {
            if (data.username) {
                // 设置 session
                req.session.username = data.username
                req.session.realname = data.realname
         
                return new SuccessModel()
            }
            return new ErrorModel('登录失败')
        })
    }

登录验证

   // 登录验证的测试
    if (method === 'GET' && req.path === '/api/user/login-test') {
        if (req.session.username) {
            return Promise.resolve(
                new SuccessModel({
                    session: req.session
                })
            )
        }
        return Promise.resolve(
            new ErrorModel('尚未登录')
        )
    }

redis

session的问题

  • 目前session是js 变量,放在nodejs进程内存中。

  • 第一,进程内存有限,访问量过大,内存暴增怎么办?

  • 第二,正式上线是多进程,进程之间无法共享。

解决方案redis

redis介绍

  • web sever最长用的缓存数据库,数据库存放在内存中

  • 相比于MySQL,访问速度快(内存和硬盘不受一个数量级别的)

  • 但是成本更高,可存储的数据更小(内存的硬伤)

方法

  • 将web sever和redis拆分为两个单独的服务

  • 双方都是独立的,都是可扩展的(例如都扩展成集群)

  • (包括MySQL,也是一个单独的服务,也可扩展)

为何session适合于redis

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

为何网站数据不适合redis

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

安装

npm i redis --save

使用

const redis = require('redis')

// 创建客户端
const redisClient = redis.createClient(6379, '127.0.0.1')
redisClient.on('error', err => {
    console.error(err)
})

// 测试
redisClient.set('myname', 'zhangsan2', redis.print)
redisClient.get('myname', (err, val) => {
    if (err) {
        console.error(err)
        return
    }
    console.log('val ', val)

    // 退出
    redisClient.quit()
})

封装工具函数

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

// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
redisClient.on('error', err => {
    console.error(err)
})

function set(key, val) {
    if (typeof val === 'object') {
        val = JSON.stringify(val)
    }
    redisClient.set(key, val, redis.print)
}

function get(key) {
    const promise = new Promise((resolve, reject) => {
        redisClient.get(key, (err, val) => {
            if (err) {
                reject(err)
                return
            }
            if (val == null) {
                resolve(null)
                return
            }

            try {
                resolve(
                    JSON.parse(val)
                )
            } catch (ex) {
                resolve(val)
            }
        })
    })
    return promise
}

module.exports = {
    set,
    get
}

解析session


// 解析 session (使用 redis)
    let needSetCookie = false
    let userId = req.cookie.userid
    if (!userId) {
        needSetCookie = true
        userId = `${Date.now()}_${Math.random()}`
        // 初始化 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
        }
        // console.log('req.session ', req.session)

        // 处理 post data
        return getPostData(req)
    })
    .then(postData => {
        req.body = postData
        //各种路由,路由转发后,根据需要写信息入cookies
        .....
    }

在登录路由中同步数据到redis

if (method === 'POST' && req.path === '/api/user/login') {
        const { username, password } = req.body
        // const { username, password } = req.query
        const result = login(username, 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 new SuccessModel()
            }
            return new ErrorModel('登录失败')
        })
    }

bolg中的登录验证

// 统一的登录验证函数
const loginCheck = (req) => {
    if (!req.session.username) {
        return Promise.resolve(
            new ErrorModel('尚未登录')
        )
    }
}

前后端联调

  • cookies跨域不共享,前端运行端口和后端运行端口不一致

  • 前端页面安装http-server,启动在8001端口,后端在8000端口

  • 用nginx统一转发至8080端口

nginx

配置 server监听8080端口