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

时间:2024-01-12 14:28:02

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 做代理 , 让前后端端同域