前言
同学,你好!我是 嘟老板 。之前发布了一篇《前端同学应该了解的 "会话控制"》,比较全面的讲述了会话控制的三种实现方式,今天我们使用 express 框架,实现一下基于 Cookie 的会话控制。
阅读本文您将收获:
了解 express 项目中,使用 cookie 涉及的依赖及应用流程。
掌握如何在 express 中通过 cookie 实现会话控制。
掌握 express Router 用法及接口定义。
注:本文涉及 express ,不了解的小伙伴可以阅读 《express 基础入门》。
本文仅涉及少量理论内容,多是代码实践,若对于技术有疑问,可评论区交流。
初始工程搭建
创建项目基础结构
- 新建项目根目录,命名为 express-explorer。
shell
mkdir express-explorer
- pnpm 初始化
shell
cd express-explorer
pnpm init
需全局安装 pnpm,
npm i -g pnpm
。
- 新建目录结构
shell
touch index.js
mkdir src
mkdir views
cd src
mkdir cookie
cd cookie
touch index.js
cd ../../views
touch login.ejs
一套命令执行下来,可以创建出以下结构:
其中:
- index.js 为项目的入口文件。
- src/cookie/index.js 为 cookie 相关接口,后续还会有 session ,jwt 等。
- views 是模板目录,其中 login.ejs 是登录页面。
安装依赖
- express: Nodejs 框架。
- cookie-parser: express 中间件,可以解析请求头的 Cookie 标头,生成以 Cookie 名称 为键的对象,填充到 request.cookies 上。
- ejs: 模板引擎,用于在 express 中渲染页面。
执行以下命令,一次性安装:
shell
pnpm add express cookie-parser ejs -S
执行成功后,查看 package.json 文件中的 dependencies,如下则表示安装成功:
编写入口代码
打开根目录的 index.js 文件,开始写代码:
- 创建 express 应用实例
javascript
const express = require('express')
// express 应用实例
const app = express()
- 安装 CookieParser 中间件
javascript
const express = require('express')
const cookieParser = require('cookie-parser')
// express 应用实例
const app = express()
// 安装 CookieParser 中间件
app.use(cookieParser())
- 启动服务,监听 3000 端口
javascript
// 定义服务启用端口号
const port = 3000
app.listen(port, () => {
console.log(`服务已启动... \n访问 http://localhost:${port}`)
})
- 写一个临时接口,测试下服务(ps: 测试通过后记得删除)。
javascript
// 测试接口
app.get('/', (req, res) => {
res.send('Hello World')
})
- 入口代码搞定,使用 nodemon 启动服务,控制台执行
nodemon index.js
,显示如下则启动成功。
nodemon 是启动 node 服务的工具,可以在检测到文件变更后自动重启服务。需要全局安装 (npm i -g nodemon) 才能在命令行直接使用。
- 浏览器访问 http://localhost:3000 看下效果。
OK,没毛病。
添加 cookie 路由
我们将 cookie 相关的路由封装进统一的 Router,便于管理,比如设置相同的接口前缀。
cookie 相关代码全部在 cookie/index.js 文件中编写。
创建并导出一个 Router 实例
javascript
const express = require('express')
// 创建 Router 实例
const router = express.Router()
module.exports = router
在根目录 index.js 中安装 cookieRouter。
javascript
const cookieRouter = require('./src/cookie')
// 安装 Cookie Router
app.use('/cookie', cookieRouter)
后续所有 CookieRouter 中定义的接口,访问时都要加上 /cookie 前缀,比如 http://localhost:3000/cookie/login。
登录接口
登录接口逻辑主要验证客户端传递的账号、密码是否正确 。若正确,则创建 Cookie 返回客户端;若不正确,则抛错提示。
方便起见,我们在代码中写死用户数据。实际项目中需要与数据库中的账号密码做匹配。
根目录下新建 users.js,保存用户数据。
javascript
/**
* 用户列表
*/
module.exports = {
dulaoban: { username: 'dulaoban', password: '123456'}
}
javascript
const users = require('../../users')
// 账号相关 cookie 名称
const COOKIES = {
USERNAME: 'username',
PWD: 'password'
}
// 登录
router.get('/login', (req, res) => {
const {name, password} = req.query
// 校验用户是否存在
const user = users[username]
if (!user) {
res.send('用户不存在')
return
}
// 匹配用户名和密码
if (username === user.username && password === user.password) {
// cookie 配置
const cookieOptions = {
httpOnly: true, // 不允许客户端修改
maxAge: 60 * 1000
}
res.cookie(COOKIES.USERNAME, username, cookieOptions)
res.send('<h1>登录成功</h1><a href="/cookie/logout">登出</a>')
} else {
res.send('账号名或密码错误')
}
})
由于 /cookie/login 是 post 接口,涉及到 payload(载荷) 的解析,我们需要安装 express 内置的中间件:urlencoded ,该中间件会解析 post 接口的请求体参数,并生成一个新对象,赋给 req.body ,接口中只需要从 req.body 中取指定参数即可。
根目录 index.js 中添加以下代码:
javascript
app.use(express.urlencoded({ extended: false }))
OK,登录接口齐活,因为是 post 接口,我们先不测试,等下一步完善登录页面后,一起看效果。
登出接口
登出接口逻辑主要清除用户相关 Cookie ,并重定向至登录页。后续客户端访问业务接口时,因为请求没带 Cookie,无法通过校验,达到会话控制的效果。
javascript
// 登出
router.get('/logout', (req, res) => {
res.clearCookie(COOKIES.USERNAME)
res.redirect('/login');
})
我们需要在根目录 index.js 中添加 login 接口,用于导航到登录页。
javascript
// 登录页
app.use('/login', (req, res) => {
res.render('login')
})
跟着写的同学,要有疑问了,这里渲染的 login 页面哪来的?
这就要用到 模板引擎 了。
首先我们在 view/login.ejs 中编写登录页面结构:
html
<h1>登录</h1>
<form method="post" action="/cookie/login">
<p>
<label for="username">用户名:</label>
<input type="text" name="username" id="username" placeholder="dulaoban">
</p>
<p>
<label for="password">密码:</label>
<input type="text" name="password" id="password" placeholder="123456">
</p>
<p>
<input type="submit" value="Login">
</p>
</form>
然后在根目录 index.js 中启用 ejs 模板引擎,并指定模板目录(views ),即 views 目录下的文件都作为页面模板处理:
javascript
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
OK,齐活,我们来看看效果。
浏览器打开 http://localhost:3000/login 打开登录页,输入用户名/密码:dulaoban/123456,点击登录。
登录成功,然后点击 Logout 按钮,退出登录。
ok,如预期一样,重定向到了登录页。
那是否真正的实现了会话控制呢,我们写个业务接口测试一下。
业务接口,对标真实项目的权限验证
业务接口逻辑主要是保证用户明确有权限的情况下,处理业务逻辑 。我们定义一个 helloWorld 的接口,若校验通过,则返回 Hello World,否则跳转登录页面。
javascript
router.get('/helloWorld', (req, res, next) => {
const { username } = req.cookies
if (!username) {
res.redirect('/login')
return
}
const user = users[username]
if (!user) {
res.send('用户不存在,请重新登录')
return
}
next()
}, (req, res) => {
res.send('Hello world')
})
以上代码中,为 helloWorld 接口增加了一个中间件函数 ,即第二个参数,用于校验请求 cookie 是否存在且合法。若校验不通过,则重新登录或抛错,否则调用 next ,继续向下执行,返回 HelloWorld。
经测试,登录状态正常显示 HelloWorld,登出状态会重定向到登录页。符合预期。
权限验证中间件
实际项目中,不会只有一个业务接口,如按照上面的写法,就要为每个接口都加上中间件函数,一旦有逻辑有调整,要挨个修改,那无疑是毁灭式的灾难。
我们可以将通用的校验逻辑抽离单独的中间件进行维护。
根目录下新建一个中间件目录:middlewares,用来专门防止中间件文件。
shell
mkdir middlewares
cd middlewares
touch checkCookieAuth.js
将校验的逻辑写入 checkCookieAuth.js 中
javascript
const users = require('../users')
function checkCookieAuth (req, res, next) {
const { username } = req.cookies
if (!username) {
res.redirect('/login')
return
}
const user = users[username]
if (!user) {
res.send('用户不存在,请重新登录')
return
}
next()
}
module.exports = checkCookieAuth
然后在 cookie/index.js 中引入,并传入 helloDulaoban 接口定义函数中。
javascript
const checkCookieAuth = require('../../middlewares/checkCookieAuth')
// 业务接口,测试
router.get('/helloWorld', checkCookieAuth, (req, res) => {
res.send('Hello world')
})
后续如果有新的接口,需要校验权限,只需要定义接口时传入 checkCookieAuth 即可。比如我又定义一个 helloDulaoban 的接口:
javascript
// 业务接口,测试
router.get('/helloDulaoban', checkCookieAuth, (req, res) => {
res.send('Hello dulaoban')
})
一处定义,到处使用,十分方便,这就是中间件的优势所在。
结语
本文重点介绍了基于 express 框架,实现 cookie 会话控制的全过程,从基础工程搭建,到入口代码,再到接口定义,以及最终的应用验证,旨在帮助同学们加深对于 Cookie 应用的理解。相关代码已上传至 GitHub,若喜欢欢迎 star 。后面会继续分享 session 和 JWT 的应用实践,感兴趣的同学蹲一下吧。
如果您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。