Skip to content
快速预览

express操作会话控制

✍️ w 🕒 2023-06-15 06:21:46(a year ago) 🔗 B.NodeJS学习 session token cookie

通过 express 设置 session token cookie

使用express 的cookie 需要使用 cookie-parser 中间件进行配合,我们可以设置cookie

  • cookieName:指定 cookie 的名称,默认为 'connect.sid'。
  • secret:用于签名 cookie 的字符串。这是确保只有服务器可以读取和修改 cookie 的一种安全措施。在服务器端生成一个私钥,并将其保存在一个安全的位置,以防止被黑客获取。当服务器向客户端发送带有加密 cookie 的响应时,它会使用此私钥来加密 cookie 值,在客户端上不可读或不可操作。
  • path:指定 cookie 的可用路径,默认为 '/',表示整个网站都可以访问该 cookie。
  • domain:指定 cookie 的域名。默认情况下,cookie 将绑定到当前访问网站的域名。
  • maxAge:指定 cookie 的最长有效期。可以使用一个毫秒值或字符串值表示。
  • httpOnly:控制是否通过客户端 JavaScript 可以访问 cookie。如果设置为 true,那么只能通过 HTTP 请求获取 cookie。
  • secure:控制是否仅通过 HTTPS 请求发送 cookie。如果设置为 true,则 cookie 将只在 HTTPS 连接中发送。
  • sameSite:指定 cookie 的浏览器限制级别。sameSite 选项有三种可能值,分别是 strict, lax 和 none。默认值是 strict。
    • Strict 时,最为严格只有从主站点设置的请求才能够携带相应的 Cookie。也就是说,如果当前用户在主站点登录,并设置了一个带 Cookie 的会话,那么这个会话只能在访问主站点的请求中携带,所有其他的第三方请求都不能够携带该 Cookie。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试访问子域名 blog.example.com 时,该请求就不能够携带来自 example.com 的 Cookie 或者 当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
    • Lax 时,主站点设置的 Cookie 可以被从该站点链接的 GET 请求所带例如 连接 预加载 GET表单等,而对于受限的非 GET 请求通过 img、iframe 等标签加载的 URL,Cookie 则不会被携带例如在第三方站点中使用 Post 方法,这些场景都不会携带 Cookie。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试访问子域名 blog.example.com 时,如果这是一个 GET 请求,那么它可以携带来自 example.com 的 Cookie,但其他类型的请求则不能够携带 Cookie。
    • None 时,任何请求都可以携带来自主站点设置的 Cookie,但是必须同时设置 Secure 属性,即 Secure 属性必须为 true。这意味着,Cookie 只能在 HTTPS 连接中使用,而不能在非 HTTPS 连接中使用。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试通过一个 HTTPS 连接访问非主站点的网站或路径时,它就可以携带来自 example.com 的 Cookie,但在非 HTTPS 连接中则不能携带。

更多参考

安装

shell
npm i cookie-parser

使用

js
// 导入express
const express = require('express')

// 导入cookie 中间件
const cookieParser = require('cookie-parser')

// 注册
const app = express()
app.use(cookieParser())

// 创建一个cookie 路由
app.get('/set-cook', (req, res) => {
	// 使用cookie key name value 是wcy  ,设置有效时间为60s
	const cookieOptions = {
		maxAge: 60 * 1000,
		httpOnly: true,
		sameSite: 'none',
		secure: true,
	}
	res.cookie('name', 'wcy', cookieOptions)
	// 设置多个key
	res.cookie('age', '18', cookieOptions)

	res.cookie('sex', '1') // 会在浏览器关闭的时候, 销毁 没设置时间的话

	res.send('123')
})

app.get('/', (res, req) => {
	console.log(res.cookies)
	req.send('789')
})


//删除 cookie
app.get('/remove-cookie', (req, res) => {
  //调用方法
  res.clearCookie('name');
  res.send('删除成功~~');
});

//获取 cookie
app.get('/get-cookie', (req, res) => {
  //获取 cookie
  console.log(req.cookies);
  res.send(`欢迎您 ${req.cookies.name}`);
})
app.listen(3000)

图 1

服务端set cookie 时候响应头信息 图 2

使用session

Session 本质还是利用了 cookies ,不同点在于cookies 在存储用户信息的时候更多是明文的key value 形式,当然也可以通过加密解密,但cookies 的容量也是有限的通常限制为 4KB(4096字节)。这包括 Cookie 的名称、值和所有属性(如过期时间、路径和域名)。

如果将cookies 的信息保存在后台中,将id 返回给前端,前端再将id 返回的时候进行反差就可以找到对应实际能容整个过程就是,当用户首次访问 Web 应用程序时,服务器会为该用户创建一个唯一的 Session,并生成一个与之关联的 Session ID。此 Session ID 通常会通过 Cookie 发送给客户端并存储在浏览器中。当用户再次访问应用程序时,浏览器会将 Session ID 发送回服务器,服务器根据 Session ID 查找对应的 Session,从而识别用户并获取其会话信息。

使用 express 中 session

js
const express = require('express')

const MongoStore = require('connect-mongo')
const app = express()

const session = require('express-session')

// 注册session 中间件
// 3. 设置 session 的中间件
app.use(
	session({
		name: 'sid', //设置cookie的name,默认值是:connect.sid
		secret: 'atguigu', //参与加密的字符串(又称签名)  加盐
		saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
		resave: true,
		store: MongoStore.create({
			mongoUrl: 'mongodb://127.0.0.1:27017/session', //数据库的连接配置
		}),
		cookie: {
			httpOnly: true, // 开启后前端无法通过 JS 操作
			maxAge: 1000 * 10, // 这一条 是控制 sessionID 的过期时间的!!!
		},
	})
)

// 设置登录 方便使用这里用get
app.get('/login', (req, res) => {
	if ((req.query.userName = 'admin' && req.query.password === 'admin')) {
		// 将 登录信息保存在session 中
		req.session.userName = 'admin'
		req.session.password = 'admin'
		req.session.uid = '258aefccc' // 用户在数据库id
		res.send('登录成功')
	} else {
		res.send('认证失败')
	}
})

//session 的读取
app.get('/cart', (req, res) => {
	//检测 session 是否存在用户数据
	if (req.session.userName) {
		res.send(`购物车页面, 欢迎您 ${req.session.userName}`)
	} else {
		res.send('您还没有登录~~')
	}
})

//session 的销毁
app.get('/logout', (req, res) => {
	req.session.destroy(() => {
		res.send('退出成功~~')
	})
})

app.listen(3000)

jwt

JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证,JWT 使 token 的生成与校验更规范,使用 jsonwebtoken 如果是 express 可以使用 expressJwt ,更多语言包

express-jwt是nodejs的一个中间件,他来验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.auth里面,然后路由到相应的router。 此模块允许您使用Node.js应用程序中的JWT令牌来验证HTTP请求。 JWT通常用于保护API端点。

express-jwt内部引用了jsonwebtoken,对其封装使用。 在实际的项目中这两个都需要引用,他们两个的定位不一样。jsonwebtoken是用来生成token给客户端的,express-jwt是用来验证token的。

正常jwt 的密钥解析 使用的是 jwt 的 verify api,也就是可以理解为 express-jwt 作为了 具有verify 功能并且是一个中间件更加方便使用

js
jwt.verify(token, '加密盐', (err, data) => {
if(err){
console.log('校验失败~~');
return
}
console.log(data);
})

自定义一个类似 express-jwt 中间件

js
//校验 token
  jwt.verify(token, secret, (err, data) => {
    //检测 token 是否正确
    if (err) {
      return res.json({
        code: '2004',
        msg: 'token 校验失败~~',
        data: null
      })
    }
    //保存用户的信息
    req.user = data; // req.session  req.body
    //如果 token 校验成功
    next();
  });
}

下面案例 我们使用黑名单效果,如果用户退出将jwt 的信息放入到黑名单中 ,关于 express-jwt api 使用

bash
npm install express express-jwt jsonwebtoken lowdb@1.0.0
js
const express = require('express')
// 引入lowdb
const low = require('lowdb')
// 使用同步写入
const FileSync = require('lowdb/adapters/FileSync')

// 导入 jwt
const { expressjwt } = require('express-jwt')
const jwt = require('jsonwebtoken')

const app = express()

// 创建文件
const adapter = new FileSync('db.json')
//获取 db 对象
const db = low(adapter)
//初始化数据 检查数据库是否已经初始化
if (!db.has('users').value() && !db.has('logout').value()) {
	// 用户信息 和 jwt 黑名单退出登录
	db.defaults({ users: [], logoutLog: [] }).write()
}

// db 注册到中间件
app.use((req, res, next) => {
	req.db = db
	next()
})

// 注册json 中间件 解决json 提交
app.use(express.json())

const privateKey = 'wcy'

// 注册express-jwt请求的 JWT 的有效性,如果有效就将 JWT 的值设置到req.user里面,然后路由到相应的router。
app.use(
	expressjwt({
		secret: privateKey, // 签名的密钥
		algorithms: ['HS256'],
		// credentialsRequired: false, // 是否允许无 Token 请求, false则对无 Token 请求不抛出异常
		// requestProperty: 'auth', // 默认解析结果会赋值在 req.auth,也可以通过 requestProperty 来修改
		// getToken(req) {req.headers.authorization}, // 自定义解析, 默认是从请求 Headers 的 Authorization 字段来获取 Token 并解析, 通过 getToken 也可以自定义一些解析逻辑,比如使用其他 Header 字段,自定义抛出异常等
		isRevoked(req, payload) {
			let jti = payload.jti
			let logout = db.get('logoutLog').find(jti).value()

			return logout
		}, // 作废 Token, 由于 Token 通常不进行存储,常用的方式是建立某个字段的黑名单(比如 TokenId),对所有 Token 进行过滤,express-jwt 专门提供了isRevoked回调来处理这种情况
	}).unless({
		path: ['/login', '/register'], // 指定路径不经过 Token 解析
	})
)

// 注册用户 api
app.post('/register', (req, res) => {
	// 获取用户提交的数据
	const { userName, password } = req.body
	// 判断用户是否已经注册
	const user = req.db.get('users').find({ userName }).value()
	if (user) {
		res.json({
			code: -1,
			message: '用户名已经存在',
		})
		return
	}
	// 将用户信息保存到数据库
	req.db.get('users').push({ userName, password }).write()
	res.json({
		code: 1,
		message: '注册成功',
	})
})

// 登录api
app.post('/login', (req, res) => {
	// 获取用户提交的数据
	const { userName, password } = req.body
	// 判断用户是否已经注册
	const user = req.db.get('users').find({ userName }).value()
	if (!user) {
		res.json({
			code: -1,
			message: '用户名不存在',
		})
		return
	}
	// 判断密码是否正确
	if (user.password !== password) {
		res.json({
			code: -1,
			message: '密码错误',
		})
		return
	}

	// 生成token
	const token = jwt.sign(
		{
			...user,
			jti: Date.now(), // 时间戳
		},
		privateKey,
		{
			expiresIn: 60 * 60 * 24 * 7, // 有效期7天
		}
	)
	// 登录成功
	res.json({
		code: 1,
		data: token,
		message: '登录成功',
	})
})

// 展示首页
app.get('/', (req, res) => {
	res.send('首页')
})

// 退出登录
app.get('/logout', (req, res) => {
	// 获取用户提交的数据
	const { userName, jti } = req.auth
	// 判断用户是否已经注册
	const user = req.db.get('users').find({ userName }).value()
	if (!user) {
		res.json({
			code: -1,
			message: '用户不存在',
		})
		return
	}
	// 将退出登录的用户信息保存到数据库
	req.db.get('logoutLog').push(jti).write()
	res.json({
		code: 1,
		message: '退出登录成功',
	})
})

app.use((err, req, res, next) => {
	console.log(err)
	res.json({
		code: -1,
		message: err.message,
	})
})

app.listen(3000)

jwt 的无感刷新 ???

OAuth2.0 ???

参考

nodejs express-jwt解析

在Node.js中使用JWT(JSON Web Token)

Express项目中: JWT使用方法

Node.js 使用 express-jwt 解析 JWT

Node鉴权系列3:JWT实现跨站点的单点登录

Released under the MIT License.