当前位置: 首页 > news >正文

Node.js 登录鉴权

目录

Session

express-session 配置

express-session 函数

ts 要配置声明文件 express-session.d.ts

express-session 使用

express-session 带角色

Token

什么是 JWT

token

jsonwebtoken 使用

jsonwebtoken 带角色


Session

express 使用 express-session 管理会话,session 是服务端存储,不会显式的返回一个 token,与客户端通过 cookie 来保持会话的标识状态。

安装

npm install express express-session
npm install --save-dev typescript @types/express @types/express-session

express-session 配置

secret

  • 用于加密 session ID 的密钥。这个密钥确保 session ID 的安全性,如果没有这个密钥,攻击者可以篡改会话 ID。你应该使用一个随机的、复杂的字符串作为 secret,以保证会话安全。不要公开你的密钥,最好将其存储在环境变量中。
  • 示例:secret: 'xxxxxxxx'

name

  • 设置会话 cookie 的名称。默认是 connect.sid,你可以修改为更符合你需求的名称。如果你有多个会话,或需要更好的安全性,可以修改 cookie 的名称来避免碰撞。
  • 示例:name: 'connect.sid'

store

  • 用于存储会话数据的存储引擎。默认情况下,会话数据保存在内存中,但对于生产环境,建议使用外部存储解决方案,如 Redis 或 MongoDB。你可以使用 session-store 兼容的第三方存储引擎。
  • 示例:store: new RedisStore({ host: 'localhost', port: 6379 })

cookie

  • 配置会话 cookie 的属性。它定义了会话 cookie 如何在客户端存储和传输。
    • path: 会话 cookie 的作用路径,默认为 '/',即适用于整个网站。
    • httpOnly: 如果为 true,客户端 JavaScript 无法访问该 cookie,增加安全性,防止 XSS 攻击。默认是 true。
    • secure: 如果为 true,cookie 只能通过 HTTPS 连接发送,适用于生产环境。默认为 false。
    • maxAge: 会话 cookie 的最大生存时间,单位为毫秒。超过该时间,cookie 会过期,默认是会话结束时过期(浏览器关闭)。
    • sameSite: 控制 cookie 是否在跨站请求时发送。strict 只会在同源请求中发送,lax 允许在某些跨站请求中发送cookie,none 允许所有请求发送,通常与 secure 一起使用。
  • 示例:cookie: {
                   httpOnly: true,
                   secure: false,  // 仅在生产环境下设置为 true,要求 HTTPS
                   maxAge: 24 * 60 * 60 * 1000,  // 1 天
                   sameSite: 'lax'
                }

resave

  • 是否在每次请求后重新保存会话,即使会话没有发生变化。如果为 true,会话每次请求都会被重新保存。对于大多数应用,推荐将其设置为 false,以提高性能。
  • 示例:resave: false

saveUninitialized

  • 是否保存未初始化的会话。当用户请求时,即使没有对会话进行任何修改,仍会保存一个空的会话。默认是 true,可以设置为 false 来避免存储不必要的空会话。建议设置 false
  • saveUninitialized: false

unset

  • 设置删除会话时的行为。
    • destroy: 会话被销毁时删除 cookie。
    • keep: 会话被销毁时不删除 cookie,即使会话被销毁,客户端仍然携带 session ID。
  • 示例:unset: 'destroy'

rolling

  • 每次请求时重新设置会话 cookie 的过期时间。如果为 true,则每次请求都会更新会话的 maxAge。如果为 false,则会话的过期时间从创建时开始计算。
  • 示例:rolling: true

genid

  • 自定义生成会话 ID 的函数。默认情况下,express-session 使用随机生成的 UUID 来作为会话 ID。如果你需要自定义会话 ID 的生成方式,可以提供该函数。
  • 示例:genid: (req) => {
                   return uuidv4();  // 使用自定义的生成逻辑
               }

secure

  • 如果为 true,则仅通过 HTTPS 请求才会发送 cookie。通常在生产环境下使用,以确保会话 cookie 的安全性。
  • 示例:secure: true,  // 生产环境下启用,确保 HTTPS 连接

示例:

app.use(session({secret: 'your_secret_key',  // 加密会话 IDresave: false,              // 不要在每次请求后重新保存会话saveUninitialized: false,   // 不保存未初始化的会话cookie: {httpOnly: true,           // 防止 JavaScript 访问secure: false,            // 开发时使用 HTTP,不启用 HTTPSmaxAge: 24 * 60 * 60 * 1000,  // 1 天有效sameSite: 'lax'           // 跨站请求时携带 cookie}
}));

express-session 函数

设置会话数据

用于登录时

// 设置会话数据
app.post('/login', (req, res) => {req.session.user = { id: 123, username: 'john' };res.send('Logged in');
});

获取会话数据

// 获取会话中的数据
app.get('/', (req, res) => {if (req.session.user) {res.send(`Hello ${req.session.user.username}`);}
});

销毁会话

调用 req.session.destroy() 来销毁当前的会话数据,这通常用于用户登出时

// 销毁会话,登出
app.post('/logout', (req, res) => {req.session.destroy((err) => {if (err) {return res.status(500).send('登出时出错');}res.send('登出');});
});

清除会话数据

如果只想删除会话中的某个字段,而不是销毁整个会话,可以通过 req.session 删除特定字段

// 清除特定会话数据
app.post('/', (req, res) => {delete req.session.user;  // 删除 user 数据res.send('User data cleared');
});

获取会话 ID

express-session 会话数据是通过会话 ID 来标识的,你可以通过 req.sessionID 获取当前请求的会话 ID

// 获取会话 ID
app.get('/', (req, res) => {res.send(`你的会话ID是 ${req.sessionID}`);
});

手动设置会话 ID

你可以通过 req.sessionID 手动设置会话 ID。不过通常会话 ID 是由 express-session 自动管理的,除非你有特殊需求

// 设置会话 ID(不常见)
app.post('/', (req, res) => {req.sessionID = 'customSessionId';res.send('Session ID set');
});

修改会话 Cookie

例如,修改 cookie 的过期时间

// 设置会话 cookie 的最大过期时间
app.post('/', (req, res) => {req.session.cookie.maxAge = 1000 * 60 * 60;  // 设置 1 小时res.send('Cookie expiration time set');
});

重新加载会话

当会话存储的内容有变化时,你可以使用 reload 来重新加载当前会话。例如用户数据被修改时

app.get('/', (req, res) => {req.session.reload((err) => {if (err) {return res.status(500).send('Failed to reload session');}res.send('Session reloaded');});
});

手动保存会话数据

如果 resave: true 修改会话数据后会自动保存,否则需要手动保存。

app.get('/', (req, res) => {req.session.user = { id: 456, username: 'alice' };req.session.save((err) => {if (err) {return res.status(500).send('Failed to save session');}res.send('Session saved');});
});

更新会话的过期时间

app.get('/', (req, res) => {req.session.touch();  // 延长会话过期时间,每次延迟时间为 + maxAgeres.send('Session touched');
});

ts 要配置声明文件 express-session.d.ts

// src/types/express-session.d.ts
import { SessionData } from 'express-session';declare module 'express-session' {interface SessionData { user?: { id?: number, username?: string, role?: any } } // 可以根据需要修改 user 的类型}

express-session 使用

import express, { Request, Response } from 'express';
import session from 'express-session';const app = express();
const port = 3000;app.use(express.json());// 会话配置
app.use(session({secret: '123456',   // 会话id密钥resave: false,      // 是否每次都重新保存会话saveUninitialized: false,   // 否保存未初始化的会话cookie: { maxAge: 1000 * 60 * 30 }  // 会话 cookie 过期时间
}));// 用户信息,一般在数据库中存储
const users = [{ id: 1, username: 'admin', password: '123456' },{ id: 2, username: 'user', password: '123456' }
];// 登录
app.get('/login', (req: Request, res: Response) => {const { username, password } = req.query;// 验证用户账号密码,真实情况,从数据库获取const user = users.find(u => u.username === username && u.password === password);if (user) {req.session.user = { username: user.username };res.send({ 'msg': '登录成功' });} else {res.status(401).json({ 'msg': '无效的用户名或密码' });}
});// 登录后用户可以访问的接口
app.get('/profile', (req: Request, res: Response) => {if (req.session.user) {res.send(`Hello ${req.session.user.username}, welcome to your profile!`);} else {res.status(401).send('You must be logged in to access this page');}
});// 注销登出
app.get('/logout', (req: Request, res: Response) => {req.session.destroy((err) => {if (err) {return res.status(500).send('Failed to destroy session');}res.send('Logout successful');});
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});

express-session 带角色

import express, { Request, Response, NextFunction } from 'express';
import session from 'express-session';const app = express();
const port = 3000;app.use(express.json());// 会话配置
app.use(session({secret: '123456',   // 会话id密钥resave: false,      // 是否每次都重新保存会话saveUninitialized: false,   // 否保存未初始化的会话cookie: { maxAge: 1000 * 60 * 30 }  // 会话 cookie 过期时间
}));// 登录接口
app.post('/login', (req: Request, res: Response) => {const { username, password } = req.body;// 假设通过某种方式验证用户名和密码,真实情况,直接查询用户信息时,带上用户的角色集合if (username === 'admin' && password === '123456') {// 登录成功,设置会话并分配角色req.session.user = { username, role: 'admin' };  // 设置角色为 'admin'res.send('登录成功');} else if (username === 'user' && password === '123456') {req.session.user = { username, role: 'user' };  // 设置角色为 'user'res.send('登录成功');} else {res.status(401).send('无效的用户或密码');}
});// 角色验证中间件
const requireRole = (role: string) => {return (req: Request, res: Response, next: NextFunction) => {if (req.session.user?.role === role) {  // 会话中的角色 和 传入的角色 是否匹配next(); // 角色匹配,继续请求} else {res.status(403).send('Forbidden: 无权限访问');}};
};// 受保护路由:只有管理员可以访问
app.get('/admin', requireRole('admin'), (req: Request, res: Response) => {res.send('Welcome to the admin dashboard');
});// 受保护路由:只有普通用户可以访问
app.get('/user', requireRole('user'), (req: Request, res: Response) => {res.send('Welcome to the user dashboard');
});// 注销接口:销毁会话
app.post('/logout', (req: Request, res: Response) => {req.session.destroy((err) => {if (err) {return res.status(500).send('Failed to destroy session');}res.send('Logout successful');});
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});

Token

express 使用 jsonwebtoken 管理 Token,jsonwebtoken 是一个用于在 Node.js 中签发和验证 JSON Web Tokens (JWT) 的库(用于生成和验证JWT)。它常用于身份验证和授权,确保 API 请求的安全性。

什么是 JWT

先介绍一下 jwt

JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种简洁、自包含的方式,用于在应用程序之间传递信息,通常用于身份验证和授权。

JWT 主要有 三部分 组成:

  • Header(头部)
  • Payload(载荷)
  • Signature(签名)

一. Header(头部)

JWT 的头部通常包含两部分信息:

  • typ(类型):JWT 类型,通常为 JWT。
  • alg(算法):用于签名 JWT 的算法。常见的算法包括:
    • HMAC SHA256 (HS256)
    • RSA (RS256)
    • ECDSA (ES256)
    • None(不使用签名算法,适用于不需要签名的情况,但很少使用)

示例:

{"alg": "HS256","typ": "JWT"
}

二. Payload(载荷)

JWT 的载荷部分包含了要传递的声明(Claims),这些声明可以是关于实体(通常是用户)的信息,以及一些附加的元数据。声明是一个 JSON 对象。以下几种类型的声明

1) 注册声明(Registered Claims)

这些是 JWT 中预定义的声明,用于提供关于 JWT 的一些标准化信息。常见的注册声明包括:

  • iss(Issuer):签发者,通常是令牌的生成方。
  • sub(Subject):主题,通常是 JWT 的主体(例如用户 ID)。
  • aud(Audience):接收者,指定令牌的目标用户或受众。
  • exp(Expiration Time):过期时间,指定 JWT 的有效期,JWT 到期后不能再使用。
  • nbf(Not Before):生效时间,指定 JWT 从何时起生效。
  • iat(Issued At):签发时间,表示 JWT 被创建的时间。
  • jti(JWT ID):JWT 的唯一标识符,常用于防止重放攻击。
2) 公共声明(Public Claims)

公共声明是用户自定义的声明,用于承载应用特定的信息(如用户id、用户权限、角色等)。

3) 私有声明(Private Claims)

私有声明是为了应用双方定义的声明,通常是为了服务之间的交互或存储应用特定的数据。这些声明没有预定义的名称,只要双方约定好了即可,也可以添加userId 或 role 等信息。

示例:

{"sub": "1234567890",      // 用户 ID"name": "John Doe",       // 用户名"role": "admin",          // 用户角色"permissions": ["read:messages","write:messages"],"iat": 1516239022,        // 签发时间"exp": 1616239022,        // 过期时间"nbf": 1516240000         // 生效时间
}

三. Signature(签名)

JWT 的签名部分是为了确保消息在传输过程中不被篡改。它是由以下部分组成的:

  • 头部(Header)
  • 载荷(Payload)

然后,用特定的签名算法(如 HMAC SHA256、RSA)和密钥对这两部分进行加密,生成签名。

签名生成过程:

  1. HeaderPayload 分别进行 Base64 编码。
  2. 将 Base64 编码的 Header 和 Payload 用点(.)连接起来:<Base64-encoded Header>.<Base64-encoded Payload>
  3. 使用指定的签名算法和密钥对上一步生成的字符串进行加密,得到签名。

示例:假设使用 HMAC SHA256 算法,密钥为 secret,我们将得到以下签名:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

一个完整的 JWT 通常由三部分组成,并且它们通过点(.)连接。即:Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT的用途

1. 身份验证(Authentication)

  • 在用户登录时,服务器会生成一个 JWT,包含用户的信息(如 ID 和角色等),并返回给客户端。
  • 客户端将 JWT 存储在本地(例如在浏览器的 LocalStorage 中或 HTTP cookie 中),每次发送请求时将 JWT 附加到 HTTP 头部(通常是 Authorization: Bearer <token>)。
  • 服务器在接收到请求时,通过解码 JWT 验证用户的身份,若有效,则允许访问相应的资源。

2. 信息传递(Information Exchange)

JWT 可以用来在不同系统或服务之间安全地传递信息。由于签名部分能够验证数据的完整性和身份,JWT 是一个理想的选择。


token

回到 Node.js 的 token

安装 jsonwebtoken

npm install express jsonwebtoken
npm install --save-dev typescript @types/express @types/jsonwebtoken

jsonwebtoken 函数

生成 JWT

jwt.sign(payload, secretOrPrivateKey, [options, callback])

参数:

  • payload:要包含在 token 中的有效载荷,通常是用户的身份信息等。
  • secretOrPrivateKey:用于签署 token 的密钥。
  • [options]:可选的配置对象,用于设置 token 的过期时间、受众、发行者等。如下:
    • expiresIn:JWT 过期时间。可以是一个数字(秒数),或是一个字符串(如 1h,30m,2d 等)。
    • notBefore:JWT 生效的时间。可以是一个数字(秒数),或是一个字符串(如 10s,1h)。
    • audience:JWT 的受众,指定该 JWT 面向的用户(例如,一个特定的服务)。
    • issuer:JWT 的签发者,用于标识生成 JWT 的主体。
    • jwtid:JWT 的唯一标识符。
    • subject:JWT 的主题(通常是用户 ID)。
    • algorithm:签名算法,默认使用 HS256。可以是其他算法,如 RS256、ES256 等。
    • keyid:密钥 ID,通常用于非对称加密。
    • header:自定义 JWT 头部信息,可以指定 typ、alg 等头部参数。
  • [callback]:可选的回调函数,如果传入,则该函数将在生成 token 后执行。

示例:

import jwt, { SignOptions } from 'jsonwebtoken';// 用户信息(载荷部分)
const payload = {sub: '123456',                  // 用户 IDname: 'Tom',                    // 用户名role: 'admin',                  // 用户角色permissions: ['read', 'write']  // 用户权限
};// 签名密钥(使用对称加密算法)
const secret = 'your-secret-key';// JWT 选项
const options: SignOptions = {expiresIn: '1h',                // 令牌有效期 1 小时notBefore: '0s',                // 立即生效audience: 'your-audience',      // 受众issuer: 'Tom',                  // 签发者jwtid: 'unique-id-12345',       // JWT 唯一标识符algorithm: 'HS256'              // 使用 HMAC SHA256 算法
};// 生成 JWT
const token = jwt.sign(payload, secret, options);
console.log('JWT:', token);

示例:        // 使用非对称加密,关于这个文件怎么生成,请查看:生成非对称密钥对

import jwt, { SignOptions } from 'jsonwebtoken';
import fs from 'fs';// 用户信息(载荷部分)
const payload = {sub: '123456',                  // 用户 IDname: 'Tom',                    // 用户名role: 'admin',                  // 用户角色permissions: ['read', 'write']  // 用户权限
};// 非对称加密私钥文件
const privateKey = fs.readFileSync('./file/es384_private_key.pem', 'utf8');// JWT 选项
const options: SignOptions = {expiresIn: '1h',                // 令牌有效期 1 小时algorithm: 'ES384'              // 使用 ECC ES384 算法
};// 生成 JWT
const token = jwt.sign(payload, privateKey, options);
console.log('JWT:', token);

验证 JWT

验证 JWT 的有效性,确保其未被篡改且未过期。如果验证成功,返回解码后的有效载荷。

jwt.verify(token, secretOrPublicKey, [options, callback])

参数:

  • token:需要验证的 JWT。
  • secretOrPublicKey:用于验证签名的密钥(对于 HMAC 使用密钥,对于 RSA 或 ECDSA 使用公钥)。
  • [options]:可选的配置对象,用于设置验证的参数,如 audience、issuer 等。如下:
    • algorithms: 用于指定可接受的算法列表,防止被使用不安全的算法进行签名验证。
    • issuer: 用于指定验证令牌时必须符合的签发者。
    • subject: 用于指定验证令牌时必须符合的主题。
    • audience: 用于指定验证令牌时必须符合的受众。
    • ignoreExpiration: 如果设置为 true,JWT 的过期时间不会被验证(默认为 false)。
    • clockTolerance: 容忍时间差,用于 exp(过期时间)字段。
    • maxAge: 设置令牌的最大有效期(例如 '1h' 表示 1 小时)。
    • jwtid: 用于指定验证令牌时必须符合的 JWT 唯一标识符。
  • [callback]:可选的回调函数,如果传入,则该函数将在验证后执行。

示例:        // 验证成功,返回 JWT 内容

import jwt from 'jsonwebtoken';// 要验证的 JWT
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiVG9tIiwicm9sZSI6ImFkbWluIiwicGVybWlzc2lvbnMiOlsicmVhZCIsIndyaXRlIl0sImlhdCI6MTc0MDMxMjY3NywibmJmIjoxNzQwMzEyNjc3LCJleHAiOjE3NDAzMTI2NzgsImF1ZCI6InlvdXItYXVkaWVuY2UiLCJpc3MiOiJUb20iLCJqdGkiOiJ1bmlxdWUtaWQtMTIzNDUifQ.WW6gGA6WcIId96BogCTFEXH9O-A0HN-rVddazqqdrb8";// 签名密钥(使用对称加密算法)
const secret = 'your-secret-key';const options = {algorithms: ['HS256' as jwt.Algorithm],         // 令牌的算法必须是 HS256issuer: 'Tom',                 // 令牌的签发者必须是 'Tom'subject: '123456',            // 令牌的主题必须是 '123456'audience: 'your-audience',     // 令牌的受众必须是 'your-audience'ignoreExpiration: false,       // 不忽略过期时间,必须验证clockTolerance: 30,            // 容忍 30 秒的时间差(比如在时钟差异较大时)maxAge: '1h',                  // 设置令牌最大有效期为 1 小时jwtid: 'unique-id-12345',      // JWT 唯一标识符必须为 'unique-id-12345'
};try {// 验证 JWT,并检查过期时间等const decoded = jwt.verify(token, secret, options);console.log('Decoded JWT:', decoded);
} catch (err) {console.error('无效的 token:', err);
}

示例:        // 使用非对称解密,关于这个文件怎么生成,请查看:生成非对称密钥对

import jwt from 'jsonwebtoken';
import fs from 'fs';// 要验证的 JWT
const token = "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiVG9tIiwicm9sZSI6ImFkbWluIiwicGVybWlzc2lvbnMiOlsicmVhZCIsIndyaXRlIl0sImlhdCI6MTc0MDM5MDQ1NiwiZXhwIjoxNzQxMjU0NDU2fQ.qaaNPijBujGCXaK9gX5OFB2k2i9LdC6IeoSMJWJFSy3lXI71_EMKCRwAZzk4RA3Rp-SUaGUuLmIMYsJ-aX6RKOSGvjBirmwECO6AzThO-og_l6R7SmM8yOIryKyrfNXC";// 非对称加密的证书文件
const publicKey = fs.readFileSync('./file/es384_cert.pem', 'utf8');const options = {algorithms: ['ES384' as jwt.Algorithm],         // 令牌的算法必须是 ECC ES384 
};try {// 验证 JWT,并检查过期时间等const decoded = jwt.verify(token, publicKey, options);console.log('Decoded JWT:', decoded);
} catch (err) {console.error('无效的 token:', err);
}

解码 JWT

解码 JWT,而不进行验证。此函数仅用于从 token 中提取载荷,而不检查其签名是否有效。适用于读取 token 内容,但不需要验证其合法性的场景。

jwt.decode(token, [options])

参数:

  • token:需要解码的 JWT。
  • [options]:可选的配置对象,设置为 complete: true 时会返回完整的 token 包括头部和载荷部分。
    • json (默认值:true): 如果为 true,返回解码后的 JSON 对象。如果为 false,返回解码后的原始字符串。
    • complete (默认值:false): 如果为 true,则返回一个完整的 JWT 对象,包含头部(header)、载荷(payload)和签名(signature)。如果为 false,只返回解码后的载荷部分。

示例:

import jwt from 'jsonwebtoken';// 要解码的 JWT
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiVG9tIiwicm9sZSI6ImFkbWluIiwicGVybWlzc2lvbnMiOlsicmVhZCIsIndyaXRlIl0sImlhdCI6MTc0MDMxMjY3NywibmJmIjoxNzQwMzEyNjc3LCJleHAiOjE3NDAzMTI2NzgsImF1ZCI6InlvdXItYXVkaWVuY2UiLCJpc3MiOiJUb20iLCJqdGkiOiJ1bmlxdWUtaWQtMTIzNDUifQ.WW6gGA6WcIId96BogCTFEXH9O-A0HN-rVddazqqdrb8";// 解码 JWT,返回完整的 JWT 对象(包含 header、payload 和 signature)
const decodedComplete = jwt.decode(token, { complete: true });
console.log('Decoded JWT (complete):', decodedComplete);

jsonwebtoken 使用

import express from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';const app = express();
const port = 3000;
const secret = 'your-secret-key';app.use(express.json());// 模拟登录接口
app.post('/login', (req, res) => {const { username, password } = req.body;// 假设用户名密码验证通过const payload = { username };// 生成 jwt const token = jwt.sign(payload, secret, { expiresIn: '1s' });// 返回 jwtres.json({ token });
});// token 验证通过才能访问的接口
app.get('/profile', (req, res) => {// 获取请求头 Authorization: Bearer <token>const token = req.headers.authorization?.split(' ')[1];if (!token) {res.status(401).send('Token is required');}try {// 验证 tokenconst decoded = jwt.verify(token as string, secret) as JwtPayload;// 把 token 中存的用户信息返回res.send(`Hello ${decoded.username}`);} catch (err) {res.status(401).send('Invalid or expired token');}
});app.listen(port, () => {console.log(`Server running at http://localhost:${port}`);
});

jsonwebtoken 带角色

import express, { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';const app = express();
const port = 3000;const secret = 'your-secret-key'; // 用于签署 JWT 的密钥app.use(express.json());// 模拟登录接口
app.post('/login', (req: Request, res: Response) => {const { username, password } = req.body;// 假设用户名密码验证通过const payload = {username,role: 'user', // 角色};// 生成 JWT,设置有效期为 1 小时const token = jwt.sign(payload, secret, { expiresIn: '1h' });// 返回生成的 JWTres.json({ token });
});// 角色验证中间件
const requireRole = (role: string) => {return (req: Request, res: Response, next: NextFunction): void => {  // 确保返回类型是 voidconst token = req.headers.authorization?.split(' ')[1]; // 提取 Bearer tokenif (!token) {res.status(401).send('Token is required');}try {// 验证并解码 JWT,获取载荷中的角色信息const decoded = jwt.verify(token as string, secret) as JwtPayload;// 检查角色是否匹配if (decoded.role !== role) {res.status(403).send('You do not have the necessary permissions');}// 如果角色验证通过,继续执行下一个中间件或路由处理函数next();} catch (err) {res.status(401).send('Invalid or expired token');}};
};// 受保护路由:只有 admin 可以访问
app.get('/profile', requireRole('admin'), (req: Request, res: Response) => {// 如果角色验证通过,执行该路由处理函数res.send('Welcome to your profile');
});// 启动服务器
app.listen(port, () => {console.log(`Server running on http://localhost:${port}`);
});

相关文章:

Node.js 登录鉴权

目录 Session express-session 配置 express-session 函数 ts 要配置声明文件 express-session.d.ts express-session 使用 express-session 带角色 Token 什么是 JWT token jsonwebtoken 使用 jsonwebtoken 带角色 Session express 使用 express-session 管理会话&…...

内存泄漏指什么?常见的内存泄漏有哪些?

内存泄漏是指程序在运行过程中&#xff0c;由于某些原因导致程序无法释放已经不再使用的内存&#xff0c;使得这部分内存持续被占用&#xff0c;最终可能导致系统可用内存逐渐减少&#xff0c;严重时会影响系统性能甚至导致程序崩溃。&#xff08;内存泄漏是指程序中已经分配的…...

【PromptCoder】使用 package.json 生成 cursorrules

【PromptCoder】使用 package.json 生成 cursorrules 在当今快节奏的开发世界中&#xff0c;效率和准确性至关重要。开发者们不断寻找能够优化工作流程、帮助他们更快编写高质量代码的工具。Cursor 作为一款 AI 驱动的代码编辑器&#xff0c;正在彻底改变我们的编程方式。但如…...

STM32的C语言软件延时函数

STM32的延时方法很多&#xff0c;其中采用定时器延时&#xff0c;可以得到较为精确的延时&#xff0c;但是有时对延时精度要求不高的场合&#xff0c;采用软件延时&#xff0c;也是必须的。特别是在RTOS系统中&#xff0c;使用SysTick的普通计数模式对延迟进行管理&#xff0c;…...

【洛谷排序算法】P1012拼数-详细讲解

洛谷 P1012 拼数这道题本身并非单纯考察某种经典排序算法&#xff08;如冒泡排序、选择排序、插入排序、快速排序、归并排序等&#xff09;的实现&#xff0c;而是在排序的基础上&#xff0c;自定义了排序的比较规则&#xff0c;属于自定义排序类型的题目。不过它借助了标准库中…...

在WINDOWS系统使用CMake gui编译NLopt配合VSCode使用

1. 准备工作 安装CMake&#xff1a;从CMake官网下载并安装CMake。下载Nlopt源码&#xff1a;从Nlopt官网或GitHub仓库下载Nlopt源码。安装编译器&#xff1a;确保已安装Visual Studio或其他支持的编译器&#xff08;如MinGW&#xff09;。 2. 配置CMake 方式1 打开CMake GU…...

angular生命周期

ngOnChanges&#xff1a;当组件的输入属性&#xff08;Input&#xff09;发生变化时调用。 ngOnInit&#xff1a;在组件的输入属性初始化后调用&#xff0c;但此时视图尚未加载。 ngAfterContentInit&#xff1a;在组件的内容投影&#xff08;ng-content&#xff09;初始化后…...

[AI概念域] AI 大模型是如何被训练出来的?(通俗解读)

说明&#xff1a;这里使用 学生成长五部曲 比喻带你理解大模型如何从零开始学会思考。 AI大模型的训练过程可分为四个核心阶段&#xff1a; 首先进行海量数据收集与清洗&#xff0c;如同为“学生”准备涵盖各领域知识的教材库&#xff1b;接着通过预训练让模型完成“填空题”…...

Mellanox的LAG全称是什么?网卡的创建机制如何?(Link Aggregation Group 链路聚合组)

背景 对于双端口的网卡&#xff0c;有时候有将链路聚合的需求。在Mellanox网卡上通过LAG提供。对于RoCE的报文在Mellanox上也可以通过LAG来完成报文收发&#xff0c;叫做RoCE over LAG。但是仅仅适用于双端口卡。 关键点 LAG&#xff1a; Link Aggregation Group (LAG) 链路…...

【最大通过数——二分】

题目 代码 #include<bits/stdc.h> using namespace std; using ll long long;const int N 2e510;int n, m, k; ll a[N], b[N];bool check(int mid) {for(int i 0; i < mid; i){if(i > n) break;if(mid-i > m) continue;if(a[i] b[mid-i] < k) return tr…...

Liunx系统中FTP与NFS

目录 一、FTP文件传输协议 1.1、FTP工作原理 1.2、FTP状态码 1.3、FTP用户类型 1.4、FTP软件vsftpd 1.4.1、安装vsftpd 1.4.2、vsftpd配置文件 二、NFS网络文件系统 2.1、NFS工作原理 2.2、NFS软件 2.3、NFS共享配置文件格式 2.4、NFS相关命令 2.4.1、exportfs 2.…...

uniapp 测试 IPA 包安装到测试 iPhone

将uniapp测试IPA包安装到测试iPhone有以下几种方法&#xff1a; 使用Xcode安装 确保计算机上安装了Xcode&#xff0c;并将iOS设备通过数据线连接到计算机。打开Xcode&#xff0c;在菜单栏中选择Window->Devices and Simulators&#xff0c;在设备列表中找到要安装的iPhone…...

结构体指针传递给函数注意事项

在 C 语言中&#xff0c;传递结构体指针给函数是一种常见且高效的编程方式。不过&#xff0c;在实际操作时&#xff0c;有一些重要的注意事项需要留意&#xff0c;下面为你详细介绍&#xff1a; 1. 避免空指针引用 在函数内部使用结构体指针前&#xff0c;要先检查该指针是否为…...

mfy学习笔记

创建表并导入数据 CREATE TABLE sales (id INT,salesperson STRING,region STRING,sales_amount INT,sale_date DATE );INSERT INTO sales (id, salesperson, region, sales_amount, sale_date) VALUES (1, Alice, North, 1000, 2023-01-01), (2, Bob, South, 1500, 2023-01-0…...

C语言--文件操作

第16讲&#xff1a;文件操作 1. 为什么使用文件&#xff1f; 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&…...

网络安全内参

在我的IT职业生涯的早期&#xff0c;Sun Microsystems被认为是一个计算机梦想家。Sun很早就创造了一个有趣的公司口号:“The network is the computer.”这是什么意思&#xff1f;这意味着IT基础设施以松散耦合的体系结构连接在一起&#xff0c;通过以太网电缆和TCP/IP协议等网…...

C++大整数类的设计与实现

1. 简介 我们知道现代的计算机大多数都是64位的&#xff0c;因此能处理最大整数为 2 64 − 1 2^{64}-1 264−1。那如果是超过了这个数怎么办呢&#xff0c;那就需要我们自己手动模拟数的加减乘除了。 2. 思路 我们可以用一个数组来存储大数&#xff0c;数组中的每一个位置表…...

在 macOS 系统上安装 kubectl

在 macOS 系统上安装 kubectl 官网&#xff1a;https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-macos/ 用 Homebrew 在 macOS 系统上安装 如果你是 macOS 系统&#xff0c;且用的是 Homebrew 包管理工具&#xff0c; 则可以用 Homebrew 安装 kubectl。 运行…...

【人工智能】蓝耘智算平台盛大发布DeepSeek满血版:开创AI推理体验新纪元

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 蓝耘智算平台 蓝耘智算平台核心技术与突破元生代推理引擎快速入门&#xff1a;三步调用大模型接口&#xff0c;OpenAI SDK无缝兼容实战用例文…...

构建数据治理闭环:DAMA视角下的全流程实践与价值变现

随着数字经济的迅速发展&#xff0c;数据已成为企业核心资产&#xff0c;高效的数据治理体系正变得至关重要。本文基于DAMA理论&#xff0c;从数据资产入表、分类分级、确权登记到元数据管理、数据质量监控&#xff0c;再到数据集成、互操作及主数据管理&#xff0c;全流程构建…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...