Nodejs第四天,身份认证

身份认证(Authentication),也被称为 "身份验证""鉴权" ,旨在通过特定手段确认用户身份。在 Web 开发中,身份认证至关重要,它保障了系统资源仅被授权用户访问。对于服务端渲染和前后端分离这两种主流开发模式,有着不同的身份认证方案。

  1. HTTP 协议的无状态性:HTTP 协议的无状态性,意味着客户端每次发起的 HTTP 请求都是相互独立的,多个连续请求之间不存在直接关联,服务器也不会主动留存每次请求的状态 。举例来说,用户在电商网站上浏览商品,第一次请求查看商品列表,第二次请求查看某一商品详情,这两个请求对于服务器而言,就像是完全不相关的两个事件,服务器无法从请求本身知晓这是同一个用户的连续操作。
  1. Cookie 的作用:Cookie 是突破 HTTP 无状态限制的关键。它由名称(Name)、值(Value)以及用于控制有效期、安全性、使用范围的可选属性构成。当客户端首次向服务器发起请求时,服务器会通过响应头向客户端发送身份认证的 Cookie,客户端会自动将其保存在浏览器中。之后,客户端每次请求服务器时,浏览器都会自动将与身份认证相关的 Cookie 通过请求头发送给服务器,服务器据此验明客户端身份。例如,用户登录论坛后,论坛服务器会给用户浏览器发送一个包含用户标识的 Cookie,当用户后续访问论坛的不同页面时,浏览器会自动带上这个 Cookie,服务器就能识别出该用户。
  1. Cookie 设置演示:在 Node.js 中,使用 Express 框架设置 Cookie 非常方便。假设我们有一个简单的 Express 应用:
javascript 复制代码
const express = require('express');
const app = express();
app.get('/setCookie', (req, res) => {
    // 设置一个名为user的Cookie,值为JohnDoe,有效期为1小时(3600秒)
    res.cookie('user', 'JohnDoe', { maxAge: 3600000 });
    res.send('Cookie set successfully');
});
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

当用户访问/setCookie路由时,服务器会在响应中设置一个名为user的 Cookie。

  1. Session 介绍:Session 是服务器端开辟的内存空间,用于在用户登录成功后存储数据。其原理类似于现实生活中的会员卡机制,用户登录时获取一张 "会员卡"(Session ID),后续凭借这张 "会员卡"(通过 Cookie 携带的 Session ID)在服务器端进行身份验证和数据交互。例如,在一个在线购物系统中,用户登录后,服务器为其创建一个 Session,记录用户的购物车信息等,用户在不同页面浏览商品、添加到购物车等操作,服务器都能通过 Session 识别用户并更新相应数据。
  1. 在 Express 中使用 Session 认证
    • 安装 express - session 中间件:在 Express 项目中,通过npm install express - session安装express - session中间件。
    • 配置 express - session 中间件:安装成功后,在应用中注册 Session 中间件,示例代码如下:
JS 复制代码
const express = require('express');
const session = require('express - session');
const app = express();
app.use(session({
    secret: 'your - secret - key', // 用于加密签名的密钥
    resave: false,
    saveUninitialized: true
}));
// 后续可以通过req.session访问和使用session对象
app.get('/storeData', (req, res) => {
    req.session.userData = { username: 'John', email: '[email protected]' };
    res.send('Data stored in session');
});
app.get('/getData', (req, res) => {
    const userData = req.session.userData;
    res.json(userData);
});
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,/storeData路由将用户数据存储在 Session 中,/getData路由从 Session 中获取并返回用户数据。

  • 清空 Session:当用户注销或需要清除 Session 数据时,可以使用以下代码:
JS 复制代码
app.get('/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            console.error('Error destroying session:', err);
        }
        res.redirect('/login'); // 重定向到登录页面
    });
});

(二)前后端分离与 JWT(Token)认证机制

  1. JWT 简介:JWT(JSON Web Token)是一种用于在网络应用中安全传输信息的开放标准(RFC 7519)。它通常由三部分组成:Header(头部)、Payload(负载)和 Signature(签名)。Header 部分包含令牌的类型(如 JWT)和使用的签名算法(如 HMAC SHA256 或 RSA),并进行 Base64Url 编码。Payload 部分携带用户的相关信息,如用户 ID、用户名等,但不建议存放敏感信息。Signature 部分用于验证消息在传输过程中未被更改,以及验证 JWT 的发送者身份。
  1. JWT 工作流程:在前后端分离的应用中,用户登录时,前端将用户的登录信息(如用户名和密码)发送到后端服务器。后端服务器验证用户信息无误后,生成一个 JWT,将其返回给前端。前端收到 JWT 后,通常会将其存储在本地(如 Local Storage 或 Cookie)。之后,前端每次向需要认证的后端 API 发送请求时,都在请求头中带上这个 JWT。后端 API 接收到请求后,验证 JWT 的有效性,如果验证通过,则处理请求并返回相应数据;如果验证失败,则返回错误信息,提示用户重新登录。
  1. 在 Node.js 中使用 JWT:首先,需要安装jsonwebtoken库,通过npm install jsonwebtoken进行安装。以下是一个简单的生成和验证 JWT 的示例:
JS 复制代码
const jwt = require('jsonwebtoken');
// 生成JWT
const generateToken = (user) => {
    const payload = {
        userId: user.id,
        username: user.username
    };
    const secretKey = 'your - secret - key';
    const options = { expiresIn: '1h' }; // 设置有效期为1小时
    return jwt.sign(payload, secretKey, options);
};
// 验证JWT
const verifyToken = (token) => {
    const secretKey = 'your - secret - key';
    try {
        const decoded = jwt.verify(token, secretKey);
        return decoded;
    } catch (error) {
        return null;
    }
};
// 示例用户数据
const user = { id: 1, username: 'JohnDoe' };
const token = generateToken(user);
console.log('Generated Token:', token);
const decodedToken = verifyToken(token);
if (decodedToken) {
    console.log('Decoded Token:', decodedToken);
} else {
    console.log('Invalid Token');
}

在实际应用中,上述代码中的生成和验证逻辑会与 Express 路由等结合,用于保护需要认证的 API 端点。

二、缓存策略

缓存(Caching)是提高应用性能的重要手段,它通过存储经常访问的数据,减少对数据源(如数据库、文件系统等)的重复访问,从而加快应用响应速度。在 Node.js 应用中,有多种缓存策略可供选择。

(一)内存缓存

  1. 原理:内存缓存是将数据存储在服务器的内存中,由于内存读写速度快,因此可以快速获取缓存数据。在 Node.js 中,可以使用一些库来实现内存缓存,如node-cache。
  1. 使用示例:首先安装node-cache库,通过npm install node-cache安装。以下是一个简单的使用示例:
JS 复制代码
const NodeCache = require('node-cache');
const myCache = new NodeCache();
// 设置缓存数据
myCache.set('key1', 'value1', 100, (err, success) => {
    if (err) {
        console.error('Error setting cache:', err);
    }
    if (success) {
        console.log('Cache set successfully');
    }
});
// 获取缓存数据
myCache.get('key1', (err, value) => {
    if (err) {
        console.error('Error getting cache:', err);
    }
    if (value) {
        console.log('Cached Value:', value);
    } else {
        console.log('Key not found in cache');
    }
});

在上述代码中,我们设置了一个键为key1,值为value1的缓存数据,有效期为 100 秒。之后通过get方法获取该缓存数据。

(二)文件缓存

  1. 原理:文件缓存是将数据存储在文件系统中,适合缓存一些相对静态且数据量较大的数据。例如,将一些生成的 HTML 页面、图片等缓存到文件系统中,下次请求相同内容时,直接从文件中读取,而无需重新生成。
  1. 使用示例:以下是一个简单的 Node.js 代码示例,用于将 HTTP 响应内容缓存到文件中:
JS 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path');
const cacheDir = 'cache';
if (!fs.existsSync(cacheDir)) {
    fs.mkdirSync(cacheDir);
}
const server = http.createServer((req, res) => {
    const cacheFilePath = path.join(cacheDir, req.url.replace(///g, '_'));
    if (fs.existsSync(cacheFilePath)) {
        // 如果缓存文件存在,直接读取并返回
        fs.readFile(cacheFilePath, 'utf8', (err, data) => {
            if (err) {
                console.error('Error reading cache file:', err);
                res.statusCode = 500;
                res.end('Internal Server Error');
            } else {
                res.end(data);
            }
        });
    } else {
        // 如果缓存文件不存在,处理请求并缓存结果
        // 这里简单模拟返回一个固定字符串作为响应
        const responseData = 'This is the response data';
        res.end(responseData);
        fs.writeFile(cacheFilePath, responseData, (err) => {
            if (err) {
                console.error('Error writing cache file:', err);
            }
        });
    }
});
const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,当请求到达时,先检查是否存在对应的缓存文件。如果存在,直接读取文件内容并返回;如果不存在,处理请求得到响应数据,返回响应数据的同时将其缓存到文件中。

(三)分布式缓存(以 Redis 为例)

  1. Redis 简介:Redis 是一个开源的内存数据结构存储系统,可用于缓存、数据库和消息代理。它支持多种数据结构,如字符串、哈希、列表、集合等,并且具有高性能、高可用性等特点,非常适合用于分布式缓存场景。
  1. 在 Node.js 中使用 Redis 进行缓存:首先安装ioredis库,通过npm install ioredis安装。以下是一个简单的示例:
JS 复制代码
const Redis = require('ioredis');
const redis = new Redis();
// 设置缓存数据
redis.set('key2', 'value2', 'EX', 100, (err, result) => {
    if (err) {
        console.error('Error setting cache in Redis:', err);
    }
    if (result === 'OK') {
        console.log('Cache set successfully in Redis');
    }
});
// 获取缓存数据
redis.get('key2', (err, value) => {
    if (err) {
        console.error('Error getting cache from Redis:', err);
    }
    if (value) {
        console.log('Cached Value from Redis:', value);
    } else {
        console.log('Key not found in Redis cache');
    }
});

在上述代码中,我们使用ioredis库连接到 Redis 服务器,设置了一个键为key2,值为value2的缓存数据,有效期为 100 秒,之后通过get方法从 Redis 中获取该缓存数据。在实际应用中,Redis 的缓存操作会与 Node.js 应用的业务逻辑紧密结合,例如缓存数据库查询结果、API 响应数据等,以提升应用整体性能。

三、Express 框架进阶

(一)中间件的深入理解与使用

  1. 中间件的概念回顾:中间件是 Express 应用的核心组成部分,它是一个函数,能够访问请求对象(req)、响应对象(res)以及应用的请求 - 响应循环中的下一个中间件函数。中间件函数可以执行任何任务,如修改请求和响应对象、结束请求 - 响应循环、调用下一个中间件等。
  1. 自定义中间件:在实际开发中,我们经常需要根据业务需求编写自定义中间件。例如,编写一个日志记录中间件,记录每个请求的相关信息:
JS 复制代码
const express = require('express');
const app = express();
// 自定义日志记录中间件
const loggerMiddleware = (req, res, next) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next();
};
app.use(loggerMiddleware);
app.get('/', (req, res) => {
    res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,loggerMiddleware中间件在每次请求到达时,将请求的时间、方法和 URL 打印到控制台,然后通过next()函数将控制权传递给下一个中间件或路由处理函数。

  1. 使用多个中间件:Express 应用可以使用多个中间件,并且中间件的执行顺序非常重要。中间件按照它们被app.use()或app.METHOD()(如app.get()、app.post()等)调用的顺序依次执行。例如:
JS 复制代码
const express = require('express');
const app = express();
const middleware1 = (req, res, next) => {
    console.log('Middleware 1');
    next();
};
const middleware2 = (req, res, next) => {
    console.log('Middleware 2');
    next();
};
app.use(middleware1);
app.use(middleware2);
app.get('/', (req, res) => {
    res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,当访问根路由/时,控制台会依次打印Middleware 1和Middleware 2,然后处理路由/的响应。

(二)路由的优化与管理

  1. 模块化路由:随着应用规模的扩大,将路由进行模块化管理可以提高代码的可维护性和可扩展性。我们可以将不同功能模块的路由分别放在不同的文件中。例如,创建一个userRoutes.js文件来管理用户相关的路由:
JS 复制代码
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => {
    res.send('List of users');
});
router.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`User with ID ${userId}`);
});
module.exports = router;

然后在主应用文件中引入并使用这些路由:

JS 复制代码
const express = require('express');
const app = express();
const userRouter = require('./userRoutes');
app.use('/api', userRouter);
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,userRoutes.js文件定义了用户相关的路由,主应用文件通过app.use('/api', userRouter)将这些路由挂载到/api路径下。这样,访问/api/users和/api/users/:id就可以触发相应的路由处理函数。

  1. 路由参数与查询参数:在 Express 路由中,路由参数(如:id)用于获取特定资源的标识,而查询参数(如?name=John&age=30)用于对资源进行筛选或传递额外信息。例如:
JS 复制代码
const express = require('express');
const app = express();
app.get('/products/:id', (req, res) => {
    const productId = req.params.id;
    res.send(`Product with ID ${productId}`);
});
app.get('/search', (req, res) => {
    const name = req.query.name;
    const price = req.query.price;
    res.send(`Searching for products with name ${name} and price ${price}`);
});
相关推荐
七月丶1 分钟前
🛠 用 Node.js 和 commander 快速搭建一个 CLI 工具骨架(gix 实战)
前端·后端·github
砖吐筷筷3 分钟前
我理想的房间是什么样的丨去明日方舟 Only 玩 - 筷筷月报#18
前端·github
七月丶4 分钟前
🔀 打造更智能的 Git 提交合并命令:gix merge 实战
前端·后端·github
iguang5 分钟前
通过实现一个mcp-server来理解mcp
前端
Lafar5 分钟前
OC-runtime使用场景
前端
sunbyte6 分钟前
Three.js + React 实战系列-3D 个人主页 :完成 Navbar 导航栏组件
开发语言·javascript·react.js
三原10 分钟前
实现多选树形组件,我把递归用明白了
前端·数据结构·vue.js
爱上大树的小猪12 分钟前
【前端样式】用 aspect-ratio 实现等比容器:视频封面与图片占位的终极解决方案
前端·css·面试
我血条呢81012 分钟前
一文带你入门 Nuxt 【俺是怎么学习一个框架的be like】
前端
博越12 分钟前
vue实现日历(仿钉钉考勤日历)
前端·javascript