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)和密钥对这两部分进行加密,生成签名。
签名生成过程:
- 将 Header 和 Payload 分别进行 Base64 编码。
- 将 Base64 编码的 Header 和 Payload 用点(
.)连接起来:<Base64-encoded Header>.<Base64-encoded Payload>。- 使用指定的签名算法和密钥对上一步生成的字符串进行加密,得到签名。
示例:假设使用 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 管理会话&…...
【c++】【线程池】固定式线程池(FixedThreadPool)
【c】【线程池】固定式线程池(FixedThreadPool) 1属性 1.1 Task可调用对象 使用 function 包装器和using类型重命名 设置一个Task的可调用对象(可理解为函数指针) 这个Task也就是我们的任务 using Task std::function<void(void)>;定义了一个…...
高可用、高性能、负载均衡集群的区别
维度高可用集群高性能集群负载均衡集群核心目标服务持续可用,减少停机加速计算任务,提升处理能力请求分发算法、健康检查关键技术冗余、心跳检测、鼓掌转移并行计算、高速网络、分布式存储请求分发算法、健康检查典型应用数据库主从切换、关键业务系统科…...
Docker 与 Serverless(无服务器架构)
Serverless(无服务器架构) 是一种新的云计算架构,它通过让开发者专注于业务逻辑而无需管理服务器基础设施,来简化应用的开发和部署。Serverless 模型通常由云服务提供商管理基础设施的所有方面,而开发者只需提供代码和…...
mac 下 java 调用 gurobi 不能加载 jar
在 mac 电脑中的 java 始终不能加载 gurobi 的 jar 包,java 的开发软件 eclipse,idea 总是显示找不到 gurobi 的 jar 包,但是 jar 包明明就在那里。 摸索了三个小时,最后发现原因竟然是: jar 包太新,替换…...
halcon三维点云数据处理(二十七)remove_bin_for_3d_object_localization
目录 一、remove_bin_for_3d_object_localization代码第一部分二、remove_bin_for_3d_object_localization代码第二部分三、效果图一、remove_bin_for_3d_object_localization代码第一部分 1、读图构建3D模型。 2、一次二值化选取区域。 3、一次和背景差值选取区域。 4、在二维…...
Python 编程题 第二节:组合数字、乘法口诀表、水仙花数、反向输出四位数、判断三角形
组合数字 1-4不重复组成三位数,利用集合的去重 lst[] for i in range(1,5):for j in range(1,5):for m in range(1,5):s{i,j,m}if len(s)3:lst.append(i*100j*10m) print(lst) 乘法口诀表 修改换行符 for i in range(1,10):for j in range(1,i1):print(f"…...
【HTML— 快速入门】HTML 基础
准备工作 vscode下载 百度网盘 Subline Text 下载 Sublime Text下载 百度网盘 vscode 下载 Sublime Text 是一款轻量好用的文本编辑器,我们在写前端代码时,使用 Sublime Text 打开比使用记事本打开,得到的代码体验更好,比 vscode…...
【MATLAB中的图像数据结构】
MATLAB中的图像数据结构 目录 MATLAB中的图像数据结构目标 :知识点 :1. 图像的存储方式 :2. 图像的颜色空间 :3. 图像的像素操作 : 示例代码 :1. 读取和显示图像 :2. 查看图像信息 :…...
在线抽奖系统——项目介绍
目录 项目介绍 页面预览 需求分析 管理员登录注册 人员模块 奖品模块 活动模块 抽奖模块 系统设计 系统架构 项目环境 数据库设计 安全设计 完整代码:项目完整代码/在线抽奖系统/lottery-system Echo/project - 码云 - 开源中国 项目介绍 利用 MySQ…...
day7作业
编写一个如下场景: 有一个英雄Hero类,私有成员,攻击(Atx),防御(Defense),速度(Speed),生命值(Blood),以及所有的set get 方…...
JavaScript 系列之:Ajax、Promise、Axios
前言 同步:会阻塞。同步代码按照编写的顺序逐行依次执行,只有当前的任务完成后,才会执行下一个任务。 异步:异步代码不会阻塞后续代码的执行。当遇到异步操作时,JavaScript 会将该操作放入任务队列中,继续…...
AI人工智能机器学习之神经网络
1、概要 本篇学习AI人工智能机器学习之神经网络,以MLPClassifier和MLPRegressor为例,从代码层面讲述最常用的神经网络模型MLP。 2、神经网络 - 简介 在 Scikit-learn 中,神经网络是通过 sklearn.neural_network 模块提供的。最常用的神经网…...
鸿蒙开发深入浅出01(基本环境搭建、页面模板与TabBar)
鸿蒙开发深入浅出01(基本环境搭建、页面模板与TabBar) 1、效果展示2、下载 DevEco Studio3、创建项目4、新建页面模板5、更改应用信息6、新建以下页面7、Index.ets8、真机运行9、图片资源文件 1、效果展示 2、下载 DevEco Studio 访问官网根据自己的版本…...
FreeRTOS动态任务和静态任务创建
一.动态任务创建 1.搭建任务框架 去task.c中将任务参数复制到main中 然后将const去掉,它会限制参数类型,任务大小、任务优先级、任务句柄需要去宏定义,任务句柄是指针类型要取地址 vTaskStartScheduler(); //开启任务调度,.c…...
QT:Graphics View的坐标系介绍
在 Qt 的 Graphics View 框架中,存在三种不同的坐标系,分别是 物品坐标系(Item Coordinates)、场景坐标系(Scene Coordinates) 和 视图坐标系(View Coordinates)。这三种坐标系在图形…...
C# httpclient 和 Flurl.Http 的测试
关于C#调用接口或Post,Flurl封装了httpclient, CSDN有哥们提供了一个公网的测试网站,可以测试Post调用,我写了2个函数,测试httpclient和Flurl使用Post: async 和 await 是成对使用的,为了接受web异步返回的数据,winfor…...
精选案例展 | 智己汽车—全栈可观测驱动智能化运营与成本优化
本案例为“观测先锋 2024 可观测平台创新应用案例大赛”精选案例,同时荣获IT168“2024技术卓越奖评选-年度创新解决方案”奖。 项目背景 近年来,中国汽车行业进入转型升级阶段,智能网联技术成为行业发展的核心。车联网、自动驾驶等技术的加速…...
阿里云可观测全面拥抱 OpenTelemetry 社区
作者:古琦 在云计算、微服务、容器化等技术重塑 IT 架构的今天,系统复杂度呈指数级增长。在此背景下,开源可观测性技术已从辅助工具演变为现代 IT 系统的"数字神经系统",为企业提供故障预警、性能优化和成本治理的全方…...
剑指 Offer II 032. 有效的变位词
comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20032.%20%E6%9C%89%E6%95%88%E7%9A%84%E5%8F%98%E4%BD%8D%E8%AF%8D/README.md 剑指 Offer II 032. 有效的变位词 题目描述 给定两个字符串 s 和 t ,…...
AcWing 蓝桥杯集训·每日一题2025·密接牛追踪2
密接牛追踪2 农夫约翰有 N 头奶牛排成一排,从左到右依次编号为 1∼N。 不幸的是,有一种传染病正在蔓延。 最开始时,只有一部分奶牛受到感染。 每经过一个晚上,受感染的牛就会将病毒传染给它左右两侧的牛(如果有的话…...
银河麒麟高级服务器操作系统在线调整/pro/{PID}/limits文件中nofile的软限制和硬限制参数值操作方法
银河麒麟高级服务器操作系统在线调整/pro/{PID}/limits文件中nofile的软限制和硬限制参数值操作方法 一 系统环境二 使用场景三 操作步骤 一 系统环境 [rootlocalhost ~]# nkvers ############## Kylin Linux Version ################# Release: Kylin Linux Advanced Server…...
山大软院ai导论实验之采用BP神经网络分类MNIST数据集
目录 实验代码 实验内容 实验代码 import matplotlib.pyplot as plt from matplotlib import font_manager import torch from torch.utils.data import DataLoader import torchvision from torchvision import transforms# 数据预处理 transform transforms.Compose([tra…...
threeJs+vue 轻松切换几何体贴图
嗨,我是小路。今天主要和大家分享的主题是“threeJsvue 轻松切换几何体贴图”。 想象一下,手头上正好有个在线3D家具商店,用户不仅可以看到产品的静态图片,还能实时更换沙发的颜色或材质,获得真实的购物体验。…...
【python】01_写在前面的话
又是爆肝干文的日子,继上次说要出一期Python新手入门教程系列文章后,就在不停地整理和码字,终于是把【基础入门】这一块给写出来了。 不积跬步无以至千里,不积小流无以成江海,一个一个板块的知识积累,早晚你…...
跨平台公式兼容性大模型提示词模板(飞书 + CSDN + Microsoft Word)
飞书云文档 CSDN MD编辑器 Microsoft Word 跨平台公式兼容方案: 一、背景痛点与解决方案 在技术文档创作中,数学公式的跨平台渲染一直存在三大痛点: 飞书云文档:原生KaTeX渲染与导出功能存在语法限制微软Word:Math…...
【Python爬虫(85)】联邦学习:爬虫数据协作的隐私保护新范式
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
深入理解 并查集LRUCaChe
并查集&LRUCaChe 个人主页:顾漂亮 文章专栏:Java数据结构 1.并查集的原理 在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后根据一定规律将归于同一组元素的…...
最新版本SpringAI接入DeepSeek大模型,并集成Mybatis
当时集成这个环境依赖冲突,搞了好久,分享一下依赖配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…...
Effective Python:(17)
Effective Python提供90条python编程技巧和秘籍,对于我们养成良好的编程习惯,减少程序出错非常重要。 这条就是一条很好的建议,即尽量使用抛出异常来处理意外情况,尽量不要用none作为返回值进行判断。问题也比较显然,如…...
