likes
comments
collection
share

🙊NodeJs进阶篇-🙈Express🙉

作者站长头像
站长
· 阅读数 31

🙊NodeJs进阶篇-🙈Express🙉

概念

Express 是 基于 Node.js 平台 , 快速、开放、极简 的 Web 开发框架 。

Express 是一个 npm 上的第三方包,它的作用和 Node.js 内置的 http 模块类似, 可以快速创建 Web 服务器

Express 的中文官网: www.expressjs.com.cn/

基本使用

① 安装Express:

npm install express
或者
yarn add express

② 创建一个新的JavaScript文件,比如app.js:

// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 调用app.listen绑定端口,启动服务器
app.listen(80, () => {
    console.log('express server running at http://127.0.0.1')
})

app.get('/', (req, res) => {
    // 通过req.query对象查询字符串形式发送给服务器的参数
    console.log(req.query)
})

app.get('/user/:id', (req, res) => {
    // 通过req.params对象匹配:形式的动态参数
    console.log(req.query)
})

③ 安装 nodemon,用于监听文件变化,自动重启服务

npm i nodemon -D

④ 配置开发启动指令

package.json 加入如下内容。

{
  "scripts": {
    "dev": "nodemon app.js"
  }
}

⑤ 启动服务

使用 npm run dev 启动项目。

打开浏览器访问 http://localhost:3000/hello,可以看到如下页面。

🙊NodeJs进阶篇-🙈Express🙉

托管静态资源

  • 通过 express.static() 方法可创建静态资源服务器,向外开放访问静态资源。
  • Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
  • 访问静态资源时,会根据托管顺序查找文件
  • 可为静态资源访问路径添加前缀

示例代码如下:

app.use(express.static('public'))
app.use(express.static('files'))
app.use('/bruce', express.static('bruce'))

/*
可直接访问 public, files 目录下的静态资源
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js

通过带有 /bruce 前缀的地址访问 bruce 目录下的文件
http://localhost:8080/bruce/images/logo.png
*/

路由

Express中的路由用于定义应用程序如何响应客户端请求的逻辑。路由由一个URI(或者路径)和一个特定的HTTP请求方法(GET、POST、PUT、DELETE等)组成。在Express中可以通过定义路由来处理客户端的请求

创建路由

  1. 首先,创建一个新的router.js文件,并在其中定义路由:
const express = require('express');
const router = express.Router();

// 定义根路径路由处理函数
router.get('/', (req, res) => {
  res.send('Home Page');
});

// 定义/about路径路由处理函数
router.get('/about', (req, res) => {
  res.send('About Page');
});

// 导出路由模块
module.exports = router;
  1. 在主应用程序中导入并使用刚刚创建的路由:
const express = require('express');
const app = express();
const router = require('./router');

// 使用路由中间件
app.use('/', router);

// 启动服务器,监听端口
const port = 3000;
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

在这个示例中,我们将两个路由处理函数定义在router.js文件中,并通过module.exports导出。在主应用程序中使用require引入router模块,并通过app.use()方法将其挂载在根路径'/'上。这样,当客户端访问根路径或者/about路径时,Express会调用相应的路由处理函数。

通过这种方式,我们可以将不同的路由定义放在不同的文件中,使代码更加清晰和模块化。这也使得我们可以在应用程序中更容易地管理和组织各个路由。

路由路径

在Express中,路由路径是指定义在路由处理程序中的URL路径模式用于匹配客户端发起的HTTP请求

路由路径可以是 字符串字符串模式正则表达式

其中字符串的方式,就是比较常规的写法。

下面是一些常见的路由路径示例:

  1. 字符串路径:最简单的形式,直接匹配指定的URL路径。
// 匹配根路径
app.get('/', (req, res) => {
  res.send('This is the root route.');
});

// 匹配/about路径
app.get('/about', (req, res) => {
  res.send('This is the about page.');
});
  1. 字符串模式:使用带有通配符的字符串模式来匹配多个路径。
// 匹配所有以 /user/ 开头的路径
app.get('/user/*', (req, res) => {
  res.send('User route.');
});

// 匹配所有以 /api/ 开头的路径
app.get('/api/*', (req, res) => {
  res.send('API route.');
});
  1. 参数化路径:使用冒号(:)定义参数化路径,允许动态获取URL中的参数。
// 匹配形如 /user/123 的路径,其中123是动态的用户ID
app.get('/user/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});
  1. 正则表达式路径:使用正则表达式定义更复杂的路径匹配规则。
// 使用正则表达式匹配以 /images/ 开头,后跟任意字符的路径
app.get(/^/images/.*/, (req, res) => {
  res.send('Image route.');
});

// 匹配任意路径
app.get('*', (req, res) => {
  res.send('404 Not Found');
});

这些是Express中常见的路由路径示例,你可以根据需要选择合适的方式来定义路由路径。

路由参数

路由参数是可以在路由路径中定义的动态部分,用于从客户端请求中提取特定的值。支持通过 queryheadersbodyparams 传递参数。

  1. 查询参数 (Query Parameters)

    查询参数通常出现在URL的"?"后面,常用于GET请求。你可以通过req.query访问这些参数。

// 客户端请求:GET /user?id=123
app.get('/user', (req, res) => {
  const userId = req.query.id;
  res.send(`User ID: ${userId}`);
});
  1. HTTP头部 (Headers)

    HTTP头部信息包含在HTTP请求的头部,用于传递客户端和服务器之间的元数据。你可以通过req.headers访问这些信息。

app.get('/headers', (req, res) => {
  const userAgent = req.headers['user-agent']; // 获取用户代理头
  res.send(`User Agent: ${userAgent}`);
});

上述例子中,我们从请求的头部信息中获取用户代理 (User-Agent),这可以用来判断客户端的类型、浏览器等。

  1. 请求体 (Body)

    请求体通常用于POST、PUT等请求,用于传递更多数据。要解析请求体,你需要使用中间件,如body-parser或Express自带的express.json()express.urlencoded()。通过req.body访问请求体中的参数。

const express = require('express');
const app = express();

app.use(express.json()); // 解析JSON请求体
app.use(express.urlencoded({ extended: true })); // 解析URL编码请求体

app.post('/submit', (req, res) => {
  const data = req.body; // 获取请求体中的数据
  res.send(`Received data: ${JSON.stringify(data)}`);
});

通过POST请求向 /submit 发送JSON数据,如 { "name": "John", "age": 30 },服务器可以从req.body获取到对应的数据。

  1. 路由参数 (Route Parameters)

    路由参数是URL路径的一部分,通过冒号:来定义。可以通过req.params访问这些参数。

// 客户端请求:GET /user/123
app.get('/user/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});

通过以上方式,可以根据具体需求选择合适的方式来传递参数,并在路由处理函数中获取参数值进行相应处理。 Express提供了方便的API来访问不同来源的参数值,以实现灵活的参数传递功能。

响应数据

在Express中,可以使用多种方法来发送响应数据给客户端。

以下是常用的几种方法:

  1. res.send(): 最常用的方法,用于发送响应数据给客户端。res.send()方法会根据数据类型自动设置Content-Type,并将数据发送给客户端。
app.get('/', (req, res) => {
  res.send('Hello, World!'); // 发送文本
  // res.send({ message: 'Hello, World!' }); // 发送JSON
});
  1. res.json(): 用于发送JSON格式的响应数据给客户端。res.json()方法会自动设置Content-Type为application/json。
app.get('/user', (req, res) => {
  res.json({ id: 1, name: 'John' }); // 发送JSON数据
});
  1. res.download() 是 Express 中用于向客户端发送文件下载的方法。它会将指定文件发送到客户端,并提供一个文件名,使得浏览器会提示用户保存文件,而不是尝试在浏览器中打开文件。
app.get('/download', function(req, res){
  const filePath = '/path/to/file.txt';
  res.download(filePath);
});

在这个例子中,当客户端访问 /download 路径时,Express 会发送位于 /path/to/file.txt 的文件到客户端,同时提示浏览器保存文件,文件名为 file.txt

  1. res.sendFile(): 用于发送文件给客户端。可以发送指定文件路径的文件给客户端。
app.get('/download', (req, res) => {
  res.sendFile('/path/to/file.txt'); // 发送文件
});
  1. res.status(): 用于设置HTTP状态码。可以在发送响应数据之前设置HTTP状态码。
app.get('/notfound', (req, res) => {
  res.status(404).send('Not Found'); // 设置状态码为404并发送消息
});
  1. res.redirect(): 用于重定向到指定的URL。
app.get('/redirect', (req, res) => {
  res.redirect('/newlocation'); // 重定向到新的URL
});
  1. res.render(): 用于渲染视图模板并发送给客户端。需要配合模板引擎(如EJS、Handlebars)使用。
// 使用EJS模板引擎
app.set('view engine', 'ejs');
app.get('/template', (req, res) => {
  res.render('template', { title: 'Express' }); // 渲染视图模板
});

使用以上方法,可以根据需要灵活地发送不同类型的响应数据给客户端,实现前后端的数据交互。 Express提供了丰富的API来处理不同类型的响应数据,以支持各种场景下的需求。

操作 header

在Express中,可以通过res.set()方法设置响应头信息。

要获取请求头(Request header),可以通过 req.headers 对象访问。

这个对象包含了所有请求头的键值对。

以下是如何获取请求头的示例代码:

app.get('/example', function(req, res){
  // 获取所有请求头
  const headers = req.headers;
  console.log(headers);

  // 获取特定请求头的值
  const userAgent = req.headers['user-agent'];
  console.log('User-Agent:', userAgent);
});

在这个例子中,req.headers 包含了所有请求头的键值对。你可以直接访问该对象来获取特定请求头的值,也可以使用点语法或方括号语法。

例如,要获取 User-Agent 请求头的值,可以使用 req.headers['user-agent']req.get('User-Agent')

使用res.set()方法设置响应头信息:

app.get('/', (req, res) => {
  res.set('Content-Type', 'text/plain'); // 设置Content-Type响应头
  res.set({
    'Cache-Control': 'no-cache',
    'Custom-Header': 'value'
  }); // 设置多个响应头
  res.send('Response with custom headers');
});

使用res.removeHeader()方法移除响应头信息:

app.get('/', (req, res) => {
  res.removeHeader('X-Powered-By'); // 移除X-Powered-By响应头
  res.send('Response with removed header');
});

这些方法可以帮助开发者在Express应用程序中方便地操作响应头信息,以满足特定的需求和业务场景。在开发过程中,根据具体情况选择合适的方法来管理和操作响应头信息是很重要的。

链式路由

app.route() 可以用来创建链式路由,可以避免重复的路由名称。

下面是一个使用链式路由的示例:

app.route('/book')
  .get(function(req, res) {
    res.send('Get a random book');
  })
  .post(function(req, res) {
    res.send('Add a book');
  })
  .put(function(req, res) {
    res.send('Update the book');
  });

在这个示例中,我们定义了一个 /book 路由,然后通过链式调用 get()post()put() 方法分别定义了对应的处理程序。这样,不同的 HTTP 方法请求同一个路由时,会触发不同的处理程序函数。

使用链式路由可以使代码更加清晰简洁,尤其是当有多个相似的路由时。

可以用于创建相同路由名称的不同请求方法,同时可以通过 all 设置所有请求的前置处理逻辑。

app
  .route('/route/any')
  .all((req, res, next) => {
    console.log('pre all', req.method, req.path)
    next()
  })
  .get((req, res) => {
    console.log('get request')
    res.send('get request')
  })
  .post((req, res) => {
    console.log('post request')
    res.send('post request')
  })

路由注册

在Express中,可以通过app.use()方法来注册路由。

路由注册是指将路由路径和相应的处理程序关联起来,以便在接收到特定路径的请求时能够执行相应的代码。

  1. 使用 app.use() 注册路由中间件: 这是最常见的方式,用于将一个或多个中间件函数绑定到一个路径上。中间件函数可以处理请求、执行某些操作,或将请求传递给下一个中间件函数。示例:
app.use('/api', apiRouter); // 将路径为 /api 的请求交给 apiRouter 处理
  1. 使用 app.METHOD() 注册特定 HTTP 方法的路由: 这种方式用于将特定 HTTP 方法(GET、POST、PUT 等)绑定到一个路径上,并指定相应的处理程序。示例:
app.get('/users', function(req, res) {
  res.send('GET request to the users');
});
  1. 使用路由对象进行模块化管理: 可以将路由定义放在单独的模块文件中,然后在主应用程序中引入并注册这些路由模块。这样可以使代码结构更清晰,方便维护和扩展。
// usersRouter.js
const express = require('express');
const router = express.Router();

router.get('/', function(req, res) {
  res.send('GET request to the users');
});

module.exports = router;
javascriptCopy Code// app.js
const express = require('express');
const usersRouter = require('./usersRouter');

const app = express();

app.use('/users', usersRouter); // 将路径为 /users 的请求交给 usersRouter 处理

嵌套路由

在Express中,可以使用嵌套路由来组织和管理路由逻辑。嵌套路由在路由中嵌套其他路由,使得代码更加可读性高和模块化。下面是一个示例,展示了如何实现嵌套路由:

const express = require('express');
const app = express();

// 主路由
const mainRouter = express.Router();

// 子路由1
const subRouter1 = express.Router();
subRouter1.get('/page1', (req, res) => {
  res.send('Subpage 1');
});

// 子路由2
const subRouter2 = express.Router();
subRouter2.get('/page2', (req, res) => {
  res.send('Subpage 2');
});

// 将子路由1添加到主路由
mainRouter.use('/sub1', subRouter1);

// 将子路由2添加到主路由
mainRouter.use('/sub2', subRouter2);

// 注册主路由
app.use('/main', mainRouter);

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在这个示例中,我们首先实例化了两个子路由subRouter1subRouter2,分别处理/sub1/page1/sub2/page2路径的请求。然后,我们将这两个子路由添加到主路由mainRouter中,分别处理/main/sub1/main/sub2路径下的请求。

最后,我们使用app.use()方法将主路由mainRouter注册到应用程序中,并指定路径为/main。这样,当访问路径/main/sub1/page1时,会触发子路由1的处理逻辑,访问路径/main/sub2/page2时,会触发子路由2的处理逻辑。

通过嵌套路由的方式,我们可以将路由逻辑分层组织,提高代码的可维护性和可扩展性。Express框架提供了强大的路由管理功能,可以帮助我们更好地组织和管理路由逻辑。

中间件

  • 中间件是指流程的中间处理环节
  • 服务器收到请求后,可先调用中间件进行预处理
  • 中间件是一个函数,包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由

中间件注意事项

  • 在注册路由之前注册中间件(错误级别中间件除外)
  • 中间件可连续调用多个
  • 别忘记调用 next() 函数
  • next() 函数后别写代码
  • 多个中间件共享 reqres对象

全局中间件

全局中间件是在Express应用中的所有路由上都能被调用的中间件(通过 app.use() 定义的中间件为全局中间件)。

全局中间件可以在每个请求被处理之前或之后执行逻辑,例如记录请求日志、处理身份验证、设置响应头等操作。

下面是一个示例,展示了如何创建和使用全局中间件:

const express = require('express');
const app = express();

// 全局中间件,打印请求日志
app.use((req, res, next) => {
  console.log(`[${new Date().toUTCString()}] ${req.method} ${req.url}`);
  next(); // 调用下一个中间件
});

// 路由
app.get('/', (req, res) => {
  res.send('Home Page');
});

app.get('/about', (req, res) => {
  res.send('About Page');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在这个示例中,我们使用app.use()方法注册了一个全局中间件,用来打印每个请求的HTTP方法和URL地址。该中间件会在每个请求到达之前被执行,因此会被所有路由使用。

全局中间件通过在应用程序级别注册,可以对整个应用程序的请求进行操作,从而减少重复的逻辑代码,并提高代码的复用性和可维护性。除了路由特定的中间件外,全局中间件是Express中另一个强大的工具,用于处理应用程序中的共同逻辑。

局部中间件

局部中间件是只会针对特定路由或路由组生效的中间件,与全局中间件相比具有更灵活的使用方式。

局部中间件可以在路由定义时指定,只会对该路由或路由组中的请求生效。

下面是一个示例,展示了如何创建和使用局部中间件:

const express = require('express')
const app = express()

// 定义中间件函数
const mw1 = (req, res, next) => {
  console.log('调用了第一个局部生效的中间件')
  next()
}

const mw2 = (req, res, next) => {
  console.log('调用了第二个局部生效的中间件')
  next()
}

// 两种定义局部中间件的方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))

app.get('/user', (req, res) => res.send('User page.'))

app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1')
})

中间件分类

在Express应用中,中间件可以按照功能和使用方式的不同进行分类。以下是常见的中间件分类:

  1. 全局中间件(Application-level Middleware)

    • 全局中间件是注册到应用程序级别的中间件,会在每个请求到达时都执行。
    • 这些中间件通常用于处理与请求处理无关的任务,如日志记录、身份验证、跨域请求处理等。
  2. 路由中间件(Router-level Middleware)

    • 局部中间件是注册到特定路由或路由组的中间件,只会在匹配到相应路由时执行。
    • 这些中间件允许为特定的路由或路由组定义定制的中间件处理逻辑。
  3. 错误处理中间件(Error-handling Middleware)

    • 错误处理中间件是专门用来处理应用程序中的错误情况。
    • 错误处理中间件通常是在中间件链的末尾,并接受四个参数 (err, req, res, next)。当一个中间件发生错误时,错误处理中间件会捕获并处理该错误,可以返回自定义错误信息或执行特定的操作。
    const express = require('express');
    const app = express();
    
    // 模拟一个会抛出错误的路由处理函数
    app.get('/error', (req, res) => {
      throw new Error('This is a simulated error');
    });
    
    // 错误处理中间件,用来捕获并处理抛出的错误
    const errorHandler = (err, req, res, next) => {
      console.error(err.stack); // 在控制台打印错误的堆栈信息
      res.status(500).send('Internal Server Error'); // 返回500状态码和错误信息
    };
    
    // 注册错误处理中间件
    app.use(errorHandler);
    
    app.listen(3000, () => {
      console.log('Server is running on port 3000');
    });
    

    在这个示例中,我们定义了一个会抛出错误的路由/error,当访问该路由时会抛出一个错误。然后,我们定义了一个错误处理中间件errorHandler,它接收四个参数(err, req, res, next),用来捕获和处理错误。在错误处理中间件中,我们打印错误的堆栈信息到控制台,并返回500状态码和错误信息给客户端。

    通过在应用程序中注册错误处理中间件,可以捕获应用程序中抛出的异常,并对其进行统一的处理,从而避免应用崩溃或泄露敏感信息给客户端。错误级别的中间件在Express应用中起着非常重要的作用,特别是对于大型或复杂的应用程序来说。

  4. 第三方中间件(Third-party Middleware):

    • 第三方中间件是由第三方库或模块提供的中间件,可以用来增强Express应用的功能。
    • 常见的第三方中间件有body-parser用于解析请求体、cors用于处理跨域请求等。
  5. 内置中间件(Built-in Middleware)

    • Express 提供了许多内置的中间件,如 express.static(用于提供静态文件服务)、express.json(用于解析 JSON 格式的请求体)等。
    • 这些中间件可以直接通过 app.use() 方法来使用,而无需额外安装或配置。

通过合理的中间件分类和组织,可以使Express应用的代码更加清晰和易于维护。不同类型的中间件可以根据需求灵活地组合和应用,以实现特定的功能和逻辑。

文件上传

在 Node.js Express 中进行文件上传通常涉及使用中间件来处理上传的文件。

常用的中间件包括 multerformidable 等。

下面是使用 multer 中间件进行文件上传的示例:

🕷️安装multer模块:

npm install multer

🐜然后,创建一个Express应用,并添加文件上传的路由:

// 引入需要的模块
import express from 'express' // 引入 Express 框架
import multer from 'multer' // 引入 Multer 模块
import fs from 'fs'
// 引入 Node.js 文件系统模块
const router = express.Router()
// 指定文件存储位置和文件名
const storage = multer.diskStorage({
  destination(req, file, cb) {
    // 这里的 destination() 函数指定了文件存储的目录
    const dir = './uploads' // './uploads' 为指定文件存储的目录
    if (!fs.existsSync(dir)) {
      // 如果该目录不存在,则创建该目录
      fs.mkdirSync(dir, { recursive: true })
    }
    cb(null, './uploads') // 将文件存储到指定目录
  },
  filename(req, file, cb) {
    // 这里的 filename() 函数指定了文件命名规则
    const ext = file.originalname.split('.').pop() // 获取文件后缀名
    cb(null, `${Date.now()}-${file.fieldname}.${ext}`) // 将文件存储到指定位置,并以指定的文件名命名
  }
})
// 创建一个 multer 实例并配置相关选项
const upload = multer({
  storage, // 存储位置和文件名规则
  limits: {
    fileSize: 1024 * 1024 * 5 // 限制文件大小为 5 MB
  },
  fileFilter(req, file, cb) {
    // 这里的 fileFilter() 函数指定了文件类型过滤规则
    // 拒绝上传非图片类型的文件
    if (!file.mimetype.startsWith('image/')) {
      const err = new Error('Only image files are allowed!') // 错误的具体信息
      err.status = 400 // 设置错误状态码为 400
      return cb(err, false)
    }
    return cb(null, true)
  }
})
// 处理文件上传请求
router.post('/upload/image', upload.single('file'), (req, res) => {
  // 这里的 upload.single() 函数指定了只上传单个文件
  res.json({ message: '文件上传成功', data: req.file }) // 返回上传成功的信息和上传的文件信息
})
export default router // 导出路由模块

代码中创建了一个Express路由对象router,使用multer.diskStorage()方法配置文件的存储位置和文件命名规则,并创建一个multer实例upload并配置相关选项,如限制文件大小和文件类型过滤规则。

在文件上传的路由/upload/image中使用upload.single('file')中间件来处理单个文件上传请求。在上传成功后,返回一个JSON响应,包含上传成功的消息和上传的文件信息。

最后,通过export default router导出路由模块,以便在主应用程序中导入并使用该文件上传路由。

🦋新建 public/upload.html 上传文件页面

<!DOCTYPE html>
<html>
<head>
  <title>上传文件示例</title>
  <meta charset="utf-8">
</head>
<body>
  <h1>上传文件示例</h1>
  <form id="upload-form" action="/upload/image" method="post" enctype="multipart/form-data">
    <div>
      <label for="file-input">选择文件</label>
      <input id="file-input" type="file" name="file">
    </div>
    <div>
      <button type="submit">上传</button>
    </div>
  </form>
</body>
</html>

🐌访问页面测试 http://localhost:3000/upload.html

CORS 跨域资源共享

cors 中间件解决跨域

  • 安装中间件:npm install cors
  • 导入中间件:const cors = require('cors')
  • 配置中间件:app.use(cors())

以下是在Node.js中使用CORS的步骤:

  1. 安装 cors 模块:首先需要安装 cors 模块,可以使用 npm 或 yarn 进行安装。
npm install cors
// 或者
yarn add cors
  1. 在Express应用中使用CORS中间件:在Express应用中引入 cors 模块,并使用中间件将其添加到应用中。以下是一个示例代码:
// 引入 Express 框架和 CORS 模块
import express from 'express';
import cors from 'cors';

// 创建 Express 应用
const app = express();

// 使用 CORS 中间件,允许所有源访问(也可以根据需求设置特定的源)
app.use(cors());

// 其他路由和中间件...
// e.g. app.get('/', (req, res) => { res.send('Hello World!'); });

// 启动应用,监听端口
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在上面的示例中,我们引入 Express 和 CORS 模块,通过 app.use(cors()) 将 CORS 中间件应用到 Express 应用中。可以根据需要对 CORS 中间件进行配置,例如指定允许哪些域名访问、允许哪些HTTP方法等。

现在,你的Express应用将允许跨域资源共享,浏览器在跨域请求时会携带 Origin 头,并经过CORS中间件的处理。这样就可以解决跨域问题,允许其他域名的访问。

身份验证-JWT 认证机制

前后端分离推荐使用 JWT(JSON Web Token)认证机制,是目前最流行的跨域认证解决方案。它可以安全地在用户和服务器之间传输信息。

JWT 由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

工作原理

用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。

🙊NodeJs进阶篇-🙈Express🙉

  1. 用户认证:当用户通过用户名和密码等方式进行身份验证后,服务器会验证用户的身份,并生成一个JWT。
  2. 生成JWT:服务器将用户的身份信息、过期时间等信息存储在JWT的Payload部分中,并在Header中指定JWT的类型和加密算法。最后结合Header和Payload,使用密钥进行签名生成JWT。
  3. 发送JWT:服务器将生成的JWT发送给客户端,通常是在HTTP的Authorization头部中发送。
  4. 客户端存储JWT:客户端收到JWT后,可以将其存储在本地,通常是在LocalStorage或SessionStorage中。
  5. 在每次请求中发送JWT:客户端在需要发送请求到服务器时,将JWT通过Authorization头部发送给服务器。
  6. 验证JWT:服务器接收到JWT后,使用相同的密钥对JWT进行解码,并验证其签名是否有效。如果验证成功,则可以信任JWT中的信息。
  7. 提取信息:从验证成功的JWT中提取Payload部分的信息,服务器可以根据JWT中的信息来验证用户的身份和权限,以及执行相应的操作。
  8. 处理过期:服务器在验证JWT时会检查JWT的过期时间,如果JWT已过期,则拒绝验证,并要求用户重新认证。

总的来说,JWT的工作原理可以简化为:认证后生成JWT -> 发送JWT给客户端 -> 客户端存储JWT -> 客户端发送JWT给服务器 -> 服务器验证JWT -> 提取信息并处理。

通过JWT,可以在客户端和服务器之间安全地传输信息,并实现无状态的身份验证,从而提高系统的安全性和可扩展性。

JWT 的结构

一个 JWT 由三部分组成,它们使用点号(.)分隔开来:

  1. Header(头部) :包含了 Token 的元数据,例如算法(HMAC SHA256 或 RSA)、token 的类型等。
  2. Payload(载荷) :包含了 token 的主要内容,通常包括用户的标识信息(如用户ID)、其他元数据或者权限信息。
  3. Signature(签名) :用于验证 token 的完整性,确保 token 在传输过程中没有被篡改。

示例

一个 JWT 的典型样式如下:

JhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

优点

  • 无状态性:JWT 包含了所有需要的信息,服务器不需要在自己的数据库中存储会话信息。
  • 跨域:JWT 可以在不同域之间安全地传输,因为它可以通过签名进行验证。
  • 可扩展性:JWT 的 Payload 部分可以包含任意的 JSON 数据,使其非常灵活。

注意事项

  • 敏感信息:尽量不要在 JWT 的 Payload 中包含敏感信息,因为 Payload 是 Base64 编码的,虽然通过签名可以保证完整性,但无法防止信息泄露。
  • 过期时间:为 JWT 设置合理的过期时间,以减少 Token 被盗用的风险。
  • HTTPS:建议在生产环境中使用 HTTPS 协议传输 JWT,以确保安全性。

Express 使用 JWT

在Express应用中使用JWT可以使用jsonwebtoken库手动生成和验证JWT,并结合express-jwt中间件来在路由中验证JWT。

以下是如何在Express应用中结合使用jsonwebtokenexpress-jwt的示例代码:

  1. 安装
  • jsonwebtoken 用于生成 JWT 字符串
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
  1. 定义 secret 密钥
  • 为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥
  • 生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串
  • 把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 密钥为任意字符串
const secretKey = 'Bruce'
  1. 生成 JWT 字符串
app.post('/api/login', (req, res) => {
  ...
  res.send({
    status: 200,
    message: '登录成功',
    // jwt.sign() 生成 JWT 字符串
    // 参数:用户信息对象、加密密钥、配置对象-token有效期
    // 尽量不保存敏感信息,因此只有用户名,没有密码
    token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
  })
})
  1. JWT 字符串还原为 JSON 对象
  • 客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
  • 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
// unless({ path: [/^/api//] }) 指定哪些接口无需访问权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^/api//] }))
  1. 获取用户信息
  • 当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息
app.get('/admin/getinfo', (req, res) => {
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取信息成功',
    data: req.user,
  })
})
  1. 捕获解析 JWT 失败后产生的错误
  • 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
  • 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.send({ status: 401, message: 'Invalid token' })
  }
  res.send({ status: 500, message: 'Unknown error' })
})

参考文献

Express

转载自:https://juejin.cn/post/7366082235244822565
评论
请登录