Node.js 从零开发 web server博客项目[登录]

web server博客项目

  1. Node.js 从零开发 web server博客项目[项目介绍]
  2. Node.js 从零开发 web server博客项目[接口]
  3. Node.js 从零开发 web server博客项目[数据存储]
  4. Node.js 从零开发 web server博客项目[登录]
  5. Node.js 从零开发 web server博客项目[日志]
  6. Node.js 从零开发 web server博客项目[安全]
  7. Node.js 从零开发 web server博客项目[express重构博客项目]
  8. Node.js 从零开发 web server博客项目[koa2重构博客项目]
  9. Node.js 从零开发 web server博客项目[上线与配置]

cookie

  • 什么是 cookie

  1. 存储在浏览器的一段字符串 ( 最大5kb )
  2. 跨域不共享
  3. 格式如 k1=v1; k2=v2; k3=v3; 因此可以存储结构化数据
  4. 每次发送 http 请求 , 会将请求域的 cookie 一起发送给 server
  5. server 可以修改 cookie 并返回给浏览器
  6. 浏览器中也可以通过 JavaScript 修改 cookie ( 有限制 )
  • JavaScript 操作 cookie , 浏览器中查看 cookie

  • 客户端查看 cookie , 三种方式

    • Network ->Reques Headers ->Cookie
    • Application -> Cookie
    • document.cookie
  • JavaScript 查看\修改 cookie ( 有限制 )

    • document.cookie = 'foo=bar;' 只能增加 cookie, 不能删除
  • server 端操作 cookie , 实现登录验证

app.js

  // 解析 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]
    const val = arr[1]
    req.cookie[key] = val
  })
  
  console.log('req.cookie is: ', req.cookie) 
  // 在客户端中 document.cookie = 'username: zhangsan;'
  // req.cookie is:  { username: 'zhangsan' }

router/user.js

 // 登录
  if (method === 'GET' && path === '/api/user/login') {
    // const {
    //   username,
    //   password
    // } = req.body

    const { username, password } = req.query
    const result = login(username, password)

    // if (result) {
    //   return new SuccessModel(result)
    // } else {
    //   return new ErrorModel('登录失败')
    // }

    return result.then(data => {
      if (data.username) {

        // 操作 cookie
        res.setHeader('Set-Cookie', `username=${data.username}; path=/`)

        return new SuccessModel()
      }
      return new ErrorModel('登录失败')
    })
  }

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

cookie做限制

在客户端当你登录成功后 ( username=lisi ) -> 删除 cookie ( username=lisi ) -> 添加 cookie ( username=zhangsan ) 会发现 用张三的账号操作的李四账号
router/user.js

const {
  login
} = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')

// 获取 cookie 的过期时间
const getCookieExpires = () => {
  const d = new Date()
  d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
  return d.toGMTString() // Mon, 08 Jul 2019 05:27:33 GMT
}

handleUserRouter = (req, res) => {
  const {
    method,
    path
  } = req

  // 登录
  if (method === 'GET' && path === '/api/user/login') {
    // const {
    //   username,
    //   password
    // } = req.body

    const { username, password } = req.query
    const result = login(username, password)

    // if (result) {
    //   return new SuccessModel(result)
    // } else {
    //   return new ErrorModel('登录失败')
    // }

    return result.then(data => {
      if (data.username) {

        // 操作 cookie
        res.setHeader('Set-Cookie', `username=${data.username}; path=/; httpOnly; expires=${getCookieExpires()}`)
        // path=/ 所有网站都生效
        // httpOnly 只允许前端更改 
        // expires 过期时间

        return new SuccessModel()
      }
      return new ErrorModel('登录失败')
    })
  }

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

module.exports = handleUserRouter

session

  • 上一节的问题 : 会暴露 username , 很危险
  • 如何解决 : cookie 中存储 userid , server 端对应 username
  • 解决方案 : session , 即 sever 端存储用户信息
    app.js
// session 数据
const SESSION_DATA = {}

// 获取 cookie 的过期时间
const getCookieExpires = () => {
  const d = new Date()
  d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
  return d.toGMTString() // Mon, 08 Jul 2019 05:27:33 GMT
}
...
  // 解析 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 路由
    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
    }

router/user.js

    return result.then(data => {
      if (data.username) {

        // 操作 cookie
        res.setHeader('Set-Cookie', `userid=${data.userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
        // path=/ 所有网站都生效
        // httpOnly 只允许前端更改 
        // expires 过期时间
        
        // 设置 session
        req.session.username = data.username
        req.session.realname = data.realname
        console.log('req.session is : ', req.session)        

        return new SuccessModel(req.session)
      }
      return new ErrorModel('登录失败')
    })
  }

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

session 存入 redis

  • cnpm i redis -S
  • conf/db.js
const env = process.env.NODE_ENV // 环境参数

// 配置
let MYSQL_CONF
let REDIS_CONF

if (env === 'dev') {
  // MySQL
  MYSQL_CONF = {
    host: 'localhost',
    user: 'root',
    password: 'root',
    port: '3306',
    database: 'myblog'
  }

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

if (env === 'production') {
  MYSQL_CONF = {
    host: 'localhost',
    user: 'root',
    password: 'root',
    port: '3306',
    database: 'myblog'
  }

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

module.exports = { MYSQL_CONF, REDIS_CONF }
  • 创建db/redis.js
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
}
  • router/blogs.js
const {
  getList,
  getDetail,
  newBlog,
  updateBlog,
  delBlog
} = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')

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

const handleBlogRouter = (req, res) => {
  const method = req.method // GET POST
  const id = req.query.id

  // 获取博客列表
  if (method === 'GET' && req.path === '/api/blog/list') {
      let author = req.query.author || ''
      const keyword = req.query.keyword || ''
      // const listData = getList(author, keyword)
      // return new SuccessModel(listData)

      if (req.query.isadmin) {
          // 管理员界面
          const loginCheckResult = loginCheck(req)
          if (loginCheckResult) {
              // 未登录
              return loginCheckResult
          }
          // 强制查询自己的博客
          author = req.session.username
      }

      const result = getList(author, keyword)
      return result.then(listData => {
          return new SuccessModel(listData)
      })
  }

  // 获取博客详情
  if (method === 'GET' && req.path === '/api/blog/detail') {
      // const data = getDetail(id)
      // return new SuccessModel(data)
      const result = getDetail(id)
      return result.then(data => {
          return new SuccessModel(data)
      })
  }

  // 新建一篇博客
  if (method === 'POST' && req.path === '/api/blog/new') {
      // const data = newBlog(req.body)
      // return new SuccessModel(data)

      const loginCheckResult = loginCheck(req)
      if (loginCheckResult) {
          // 未登录
          return loginCheckResult
      }

      req.body.author = req.session.username
      const result = newBlog(req.body)
      return result.then(data => {
          return new SuccessModel(data)
      })
  }

  // 更新一篇博客
  if (method === 'POST' && req.path === '/api/blog/update') {
      const loginCheckResult = loginCheck(req)
      if (loginCheckResult) {
          // 未登录
          return loginCheckResult
      }

      const result = updateBlog(id, req.body)
      return result.then(val => {
          if (val) {
              return new SuccessModel()
          } else {
              return new ErrorModel('更新博客失败')
          }
      })
  }

  // 删除一篇博客
  if (method === 'POST' && req.path === '/api/blog/del') {
      const loginCheckResult = loginCheck(req)
      if (loginCheckResult) {
          // 未登录
          return loginCheckResult
      }

      const author = req.session.username
      const result = delBlog(id, author)
      return result.then(val => {
          if (val) {
              return new SuccessModel()
          } else {
              return new ErrorModel('删除博客失败')
          }
      })
  }
}

module.exports = handleBlogRouter
  • router/user.js
const { login } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
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 { 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
                // console.log(req.session)
                set(req.sessionId, req.session)

                return new SuccessModel()
            }
            return new ErrorModel('登录失败')
        })
    }
}

module.exports = handleUserRouter

  • 和前端联调

  • 登录功能依赖 cookie , 必须用浏览器来联调
  • cookie 跨域不共享 , 前端和 server 端必须同域
  • 需要用到 nginx 做代理 , 让前后端端同域
posted @ 2019-07-10 11:04  仲灏  阅读(250)  评论(0编辑  收藏  举报