在现代 Web 应用程序中,认证和授权是不可避免的话题。Koa2 是一款流行的 Node.js Web 框架,并且可以非常方便地实现认证和授权功能。本文将详细介绍如何使用 Koa2 构建一个带有认证和授权的 Web 应用程序,并提供示例代码来指导读者实践。
准备工作
在开始之前,确保你已经安装了 Node.js 和 npm。如果没有,请到 Node.js 官网下载并安装。
在一个新的目录中创建一个空的项目:
mkdir koa-auth cd koa-auth npm init -y
接下来,安装需要的依赖:
npm install koa koa-router koa-bodyparser jsonwebtoken bcryptjs npm install --save-dev nodemon
以上命令安装了 Koa2、Koa2 路由、Koa2 bodyparser、JSON Web Token、bcryptjs(用于密码哈希)和 nodemon(用于自动重启服务器)。
编写代码
应用程序结构
接下来我们来设置应用程序的基本结构,项目结构如下。
// javascriptcn.com 代码示例 koa-auth ├── node_modules ├── src │ ├── controllers │ ├── db │ ├── models │ ├── routes │ └── utils ├── package.json └── app.js
其中:
- controllers:存放路由逻辑代码
- db:存放数据库连接和操作代码
- models:存放数据模型定义代码
- routes:存放路由定义代码
- utils:存放工具函数代码
- app.js:应用程序入口文件
数据库设置
首先,让我们来连接到 MongoDB 数据库。在 src/db/index.js
中添加以下代码:
// javascriptcn.com 代码示例 const mongoose = require('mongoose'); const connectDB = async () => { try { const conn = await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, }); console.log(`MongoDB Connected: ${conn.connection.host}`); } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } }; module.exports = connectDB;
这段代码中,我们使用 Mongoose 连接到 MongoDB 数据库,并且输出连接的结果。接下来,在应用程序的入口文件 app.js
中使用以上代码来连接数据库:
// javascriptcn.com 代码示例 require('dotenv').config(); const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); const connectDB = require('./src/db'); const app = new Koa(); // 解析请求体 app.use(bodyParser()); // 连接到数据库 connectDB(); app.listen(process.env.PORT, () => console.log(`Server listening on port ${process.env.PORT}...`) );
在 app.js
中,我们调用 connectDB()
函数来连接到数据库,同时也配置了请求体解析中间件和启动服务器。
用户模型
我们需要一个用户模型来存储用户的用户名和密码。在 src/models/User.js
中添加以下代码:
// javascriptcn.com 代码示例 const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const UserSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true, }, password: { type: String, required: true, }, }); // 保存用户之前,对密码进行哈希 UserSchema.pre('save', async function (next) { if (!this.isModified('password')) { return next(); } const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); }); // 检查用户输入的密码是否正确 UserSchema.methods.checkPassword = async function (password) { return await bcrypt.compare(password, this.password); }; module.exports = mongoose.model('User', UserSchema);
在以上代码中,我们定义了一个用户模型,模型有两个属性:username
和 password
。为了保护用户的密码,我们使用 bcryptjs
库来对密码进行哈希。在保存用户之前,我们使用 pre
钩子来对密码进行哈希;在校验用户输入的密码时,我们使用 checkPassword
方法来校验密码是否正确。
用户路由
接下来,我们来定义用户路由。在 src/routes/user.js
中添加以下代码:
// javascriptcn.com 代码示例 const Router = require('koa-router'); const jwt = require('jsonwebtoken'); const User = require('../models/User'); const { getTokenPayload } = require('../utils/auth'); const router = new Router({ prefix: '/api/user' }); // 注册用户 router.post('/register', async (ctx) => { const { username, password } = ctx.request.body; if (!username || !password) { ctx.throw(400, '用户名或密码不能为空'); } const user = new User({ username, password }); try { await user.save(); ctx.body = { message: '注册成功' }; } catch (err) { ctx.throw(400, err.message); } }); // 用户登录 router.post('/login', async (ctx) => { const { username, password } = ctx.request.body; const user = await User.findOne({ username }); if (!user || !(await user.checkPassword(password))) { ctx.throw(400, '用户名或密码不正确'); } const token = jwt.sign({ username }, process.env.JWT_SECRET); ctx.body = { token }; }); // 获取当前登录用户 router.get('/me', async (ctx) => { const tokenHeader = ctx.request.headers.authorization; const token = getTokenPayload(tokenHeader); const user = await User.findOne({ username: token.username }); if (!user) { ctx.throw(401, '无权限访问'); } ctx.body = { username: user.username }; }); module.exports = router;
在用户路由中,我们定义了三个接口:
/api/user/register
:注册用户接口,接受用户名和密码,将用户保存到数据库中/api/user/login
:用户登录接口,接受用户名和密码,检查用户名和密码是否正确,并且返回一个 JSON Web Token/api/user/me
:获取当前登录用户的接口,需要用户传递 JSON Web Token,通过解析 Token 来获取当前登录的用户
在以上代码中,我们使用了 jsonwebtoken
库来进行 Token 签名和解析,使用了 getUserFromToken
函数来解析 Token,从而获取解码后的用户名。此外,还使用了 ctx.throw()
函数来抛出错误,用于统一错误处理。
应用入口
在应用程序入口文件 app.js
中,我们导入并使用路由,如下所示:
// javascriptcn.com 代码示例 require('dotenv').config(); const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); const cors = require('@koa/cors'); const connectDB = require('./src/db'); const userRouter = require('./src/routes/user'); const app = new Koa(); // 解析请求体 app.use(bodyParser()); // 跨域处理 app.use(cors()); // 连接到数据库 connectDB(); // 使用路由 app.use(userRouter.routes()); app.listen(process.env.PORT, () => console.log(`Server listening on port ${process.env.PORT}...`) );
在 app.js
中,我们同时使用了 koa-cors
库来实现跨域处理,以便于能够使用不同的域名或端口来访问本地 Web 服务。
鉴权中间件
我们需要编写一个授权验证的中间件,以确保只有登录的用户才能访问资源。在 src/utils/auth.js
中添加以下代码:
// javascriptcn.com 代码示例 const jwt = require('jsonwebtoken'); const getTokenPayload = (tokenHeader) => { const [, token] = tokenHeader.split(' '); return jwt.verify(token, process.env.JWT_SECRET); }; const auth = async (ctx, next) => { const tokenHeader = ctx.request.headers.authorization; if (!tokenHeader) { ctx.throw(401, 'Token 缺失'); } try { const token = getTokenPayload(tokenHeader); ctx.state.user = token.username; } catch (err) { ctx.throw(401, 'Token 不合法'); } await next(); }; module.exports = { auth, getTokenPayload };
在以上代码中,我们使用 getTokenPayload
函数来解析 Token,并且将解密后的用户名写入 Koa 的状态中。这使得在其他中间件中,我们可以轻松地访问到解密后的用户名。
保护资源路由
最后,我们需要编写一个受保护的路由来展示如何使用鉴权中间件。在 src/routes/protection.js
中添加以下代码:
// javascriptcn.com 代码示例 const Router = require('koa-router'); const { auth } = require('../utils/auth'); const router = new Router({ prefix: '/api/protection' }); router.get('/', auth, async (ctx) => { ctx.body = { message: '该路由仅对已登录的用户开放' }; }); module.exports = router;
在资源路由中,我们只需要使用 auth
中间件来确保用户已经登录,并且可以直接读取 Koa 的状态中存储的当前登录用户名,从而进行更复杂的鉴权操作。
总结
在本文中,我们详细介绍了如何使用 Koa2 构建一个带有认证和授权的 Web 应用程序。我们展示了如何使用 MongoDB 来存储用户信息、如何生成和解码 JSON Web Token、如何编写鉴权中间件以及如何保护一些受保护的资源。我们相信本文对于初学者具有较强的指导意义,并且提供了一些非常有价值的示例代码供读者参考和学习。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65863ee3d2f5e1655d0a083a