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 服务交互了:
比如set
、get
、keys *
、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 就可以解决这些问题。