news 2026/4/20 13:18:19

Web学习之用户认证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Web学习之用户认证

一、Cookie 和 Session 的存储内容对比

  1. Cookie 中存储什么信息
    本质:Cookie 只存储一个 Session ID(会话标识符)

具体内容:

// 一个典型的 Cookie 示例(开发者工具中查看)Name:sessionIdValue:s%3Aabc123def456.session_secret_signature// 解码后:s:abc123def456.session_secret_signature// s: 是 express-session 的前缀,abc123def456 是真正的 session ID// Cookie 的其他关键字段:Domain:.example.com// 作用域Path:/// 路径Expires/Max-Age:时间戳// 过期时间Secure:true// 仅 HTTPSHttpOnly:true// 防止 JS 访问SameSite:Lax// 跨站策略

为什么 Cookie 只存 ID?

安全考虑:Cookie 存储在用户浏览器,可能被窃取

大小限制:Cookie 一般限制在 4KB

网络性能:每次请求都会自动发送 Cookie

  1. Session 中存储什么信息
    本质:Session 存储在服务器端,保存用户的完整会话数据

具体内容:

// 一个典型的 session 数据结构(存储在 Redis 中){"sess:abc123def456":{"userId":"123456","username":"john_doe","email":"john@example.com","roles":["user","admin"],"loginTime":"2024-01-15T10:30:00Z","lastActivity":"2024-01-15T11:15:00Z","cart":[{"productId":"p001","quantity":2},{"productId":"p002","quantity":1}],"preferences":{"theme":"dark","language":"zh-CN","notifications":true},"csrfToken":"x678y9z0",// 防止 CSRF 攻击"_expire":1705313700000// 过期时间戳}}

Session 中常见的数据类型:

认证信息:userId, username, email

权限信息:roles, permissions

会话状态:loginTime, lastActivity

业务数据:购物车、表单草稿、临时数据

安全令牌:csrfToken, oauthState

用户偏好:主题、语言设置

二、存储位置详解

  1. Cookie 存储位置
    位置:客户端浏览器中

具体存储:

浏览器存储位置: ├── 内存 Cookie(Session Cookie) │ └── 浏览器关闭即删除 │ ├── 硬盘 Cookie(持久 Cookie) │ └── 按过期时间存储在硬盘 │ └── 按域和路径组织 ├── example.com/ │ ├── sessionId │ ├── userPref │ └── trackingId └── api.example.com/ └── authToken

查看方式(浏览器开发者工具):

Chrome: Application → Storage → Cookies

Firefox: Storage → Cookies

命令行: document.cookie(仅限非 HttpOnly Cookie)

  1. Session 存储位置
    位置:服务器端

存储介质:

// 1. 内存存储(默认,不推荐生产环境)constsession=require('express-session');app.use(session({secret:'keyboard cat',resave:false,saveUninitialized:true}));// 2. Redis 存储(推荐生产环境)constRedisStore=require('connect-redis')(session);app.use(session({store:newRedisStore({host:'localhost',port:6379,prefix:'sess:',// Redis key 前缀ttl:86400// 过期时间(秒)}),secret:'your-secret'}));// 3. 数据库存储(MySQL/PostgreSQL/MongoDB)constMongoStore=require('connect-mongo');app.use(session({store:MongoStore.create({mongoUrl:'mongodb://localhost/sessions',ttl:14*24*60*60// 14天}),secret:'your-secret'}));

Redis 中的实际存储结构:

# 查看所有 sessionredis-cli keys"sess:*"# 查看具体 session 内容redis-cli get"sess:abc123def456"# 返回 JSON 字符串,包含所有 session 数据

三、完整实现流程详解

流程图

客户端 服务器 | | |--- 1. 登录请求 --------->| | | |<-- 2. 创建 session -----| | Set-Cookie: sessionId| | | |--- 3. 带 Cookie 请求 --->| | Cookie: sessionId | | | |<-- 4. 验证 session -----| | 返回 session 数据 | | | |--- 5. 后续请求 --------->| | (自动携带 Cookie) | | | |<-- 6. 验证/更新 session | | | |--- 7. 登出请求 --------->| | | |<-- 8. 销毁 session -----| | 清除 Cookie |

详细步骤代码实现

constexpress=require('express');constsession=require('express-session');constRedisStore=require('connect-redis')(session);constapp=express();// 1. Session 中间件配置app.use(session({store:newRedisStore({host:'127.0.0.1',port:6379,prefix:'sess:',ttl:1800// 30分钟}),name:'sessionId',// Cookie 名称,默认 'connect.sid'secret:'complex-secret-key-change-in-production',resave:false,// 避免 session 被覆盖saveUninitialized:false,// 不保存空的 sessioncookie:{// Cookie 相关设置maxAge:30*60*1000,// 30分钟(毫秒)secure:process.env.NODE_ENV==='production',// 仅 HTTPShttpOnly:true,// 防止 XSSsameSite:'lax',// CSRF 防护path:'/',// Cookie 路径domain:'.example.com'// 作用域},rolling:true,// 每次请求重置过期时间genid:function(req){// 生成唯一的 session IDreturnrequire('crypto').randomBytes(16).toString('hex');}}));// 2. 用户登录 - 创建 sessionapp.post('/api/login',async(req,res)=>{const{username,password}=req.body;// 验证用户凭证constuser=awaitauthenticateUser(username,password);if(user){// 在 session 中存储用户信息req.session.userId=user.id;req.session.username=user.username;req.session.roles=user.roles;req.session.loginTime=newDate();req.session.lastActivity=newDate();// 生成 CSRF tokenreq.session.csrfToken=require('crypto').randomBytes(32).toString('hex');// 设置购物车(如果之前有)if(!req.session.cart){req.session.cart=[];}// 发送响应(Cookie 会自动通过 Set-Cookie 头发送)res.json({success:true,message:'登录成功',user:{id:user.id,username:user.username},// 如果需要,可以在响应中返回 CSRF tokencsrfToken:req.session.csrfToken});}else{res.status(401).json({success:false,message:'认证失败'});}});// 3. Session 验证中间件functionrequireAuth(req,res,next){// 检查 session 是否存在且包含用户信息if(!req.session||!req.session.userId){returnres.status(401).json({error:'未授权,请先登录'});}// 更新最后活动时间req.session.lastActivity=newDate();// 检查 CSRF token(对于 POST/PUT/DELETE 请求)if(['POST','PUT','DELETE'].includes(req.method)){constclientToken=req.headers['x-csrf-token']||req.body._csrf;if(clientToken!==req.session.csrfToken){returnres.status(403).json({error:'CSRF token 无效'});}}// 将用户信息附加到请求对象req.user={id:req.session.userId,username:req.session.username,roles:req.session.roles};next();}// 4. 受保护的路由app.get('/api/profile',requireAuth,(req,res)=>{// req.user 已在中间件中设置res.json({user:req.user,loginTime:req.session.loginTime,lastActivity:req.session.lastActivity});});// 5. 更新 session 数据(如购物车)app.post('/api/cart/add',requireAuth,(req,res)=>{const{productId,quantity}=req.body;// 确保购物车存在if(!req.session.cart){req.session.cart=[];}// 查找商品是否已在购物车constexistingItem=req.session.cart.find(item=>item.productId===productId);if(existingItem){existingItem.quantity+=quantity;}else{req.session.cart.push({productId,quantity});}// 手动保存 session(当修改了 session 时)req.session.save((err)=>{if(err){console.error('保存 session 失败:',err);returnres.status(500).json({error:'服务器错误'});}res.json({success:true,cart:req.session.cart});});});// 6. 用户登出 - 销毁 sessionapp.post('/api/logout',(req,res)=>{// 获取 session ID(用于清理其他存储)constsessionId=req.session.id;// 销毁 sessionreq.session.destroy((err)=>{if(err){console.error('销毁 session 失败:',err);returnres.status(500).json({error:'登出失败'});}// 清除客户端的 Cookieres.clearCookie('sessionId',{path:'/',domain:'.example.com'});// 可选:清理其他相关存储cleanupSessionData(sessionId);res.json({success:true,message:'登出成功'});});});// 7. Session 清理任务(定时任务)functioncleanupExpiredSessions(){// Redis 会自动清理过期的 session(基于 TTL)// 对于数据库存储,可能需要定时任务setInterval(async()=>{constexpiredSessions=awaitSessionModel.find({expires:{$lt:newDate()}});// 清理过期 sessionawaitSessionModel.deleteMany({_id:{$in:expiredSessions.map(s=>s._id)}});console.log(`清理了${expiredSessions.length}个过期 session`);},3600000);// 每小时清理一次}

四、关键字段和作用详解

Cookie 字段配置表

字段值示例作用重要性详细说明
NamesessionIdCookie 名称★★★标识 cookie 的键名,服务器通过此名称读取 cookie 值
Values%3Aabc123加密的 session ID★★★经过编码和签名的 session ID,是连接客户端和服务器的关键凭证
Domain.example.com作用域★★★指定 cookie 有效的域名,. 开头表示所有子域名共享
Path/Cookie 有效的路径★★指定 cookie 在网站中的有效路径,/ 表示全站有效
Expires/Max-Age2024-01-16T10:30:00Z过期时间★★★控制 cookie 的存活时间,过期后浏览器自动删除
Securetrue仅通过 HTTPS 传输★★★防止 cookie 在明文中传输被窃取,生产环境必须启用
HttpOnlytrue禁止 JavaScript 访问★★★防止 XSS 攻击窃取 cookie,敏感 cookie 必须设置
SameSiteLax/StrictCSRF 防护策略★★★控制跨站请求是否携带 cookie,有效防止 CSRF 攻击

Session 配置字段表

字段默认值作用推荐设置详细说明
nameconnect.sidCookie 名称自定义名称避免使用默认名称,提高安全性,防止攻击者猜测
secret-签名密钥长且复杂的随机字符串用于签名 session ID,防止篡改,定期更换
resavetrue强制保存 sessionfalse避免每次请求都保存 session,减少竞争条件
saveUninitializedtrue保存空 sessionfalse不保存未修改的 session,节省存储空间
cookie.securefalse仅 HTTPS生产环境设为 true确保 cookie 只在安全连接中传输
cookie.httpOnlytrue防 XSS始终 true防止 JavaScript 访问敏感 cookie
cookie.sameSitefalse跨站策略lax 或 strictstrict:完全禁止跨站;lax:部分允许
cookie.maxAgenullCookie 过期时间根据业务设置单位毫秒,如 30 分钟:30 * 60 * 1000
rollingfalse重置过期时间true用户每次活动都延长 session 有效期
store内存Session 存储Redis(生产环境)生产环境必须使用外部存储,支持集群部署

Session 数据字段表

字段名类型存储内容生命周期详细说明
userIdString用户唯一标识登录到登出用于关联数据库中的用户记录,最关键的字段
usernameString用户名登录到登出显示用途,避免频繁查询数据库
rolesArray用户角色权限登录到登出如 [“user”, “admin”],用于权限控制
loginTimeDate登录时间登录到登出记录用户登录时刻,用于审计和安全分析
lastActivityDate最后活动时间每次请求更新用于判断用户是否活跃,实现自动登出
csrfTokenStringCSRF 防护令牌每次会话随机生成的 token,防止跨站请求伪造攻击
cartArray购物车数据会话期间存储用户临时的购物车信息,如 [{productId, quantity}]
preferencesObject用户偏好会话期间如主题、语言等个性化设置
tempDataAny临时数据短期存储表单数据、验证码等临时信息,定期清理

五、安全最佳实践

  1. Cookie 安全设置
app.use(session({// ... 其他配置cookie:{secure:true,// 强制 HTTPShttpOnly:true,// 防 XSSsameSite:'strict',// 防 CSRFdomain:'.yourdomain.com',// 限制域名path:'/',// 限制路径maxAge:30*60*1000// 合理过期时间}}));
  1. Session 数据清理
// 定期清理过期 sessionfunctioncleanOldSessions(){// 删除超过30天未活动的 sessionconstcutoff=Date.now()-(30*24*60*60*1000);// Redis 示例redisClient.keys('sess:*',(err,keys)=>{keys.forEach(key=>{redisClient.get(key,(err,sessionStr)=>{if(sessionStr){constsession=JSON.parse(sessionStr);if(session.lastActivity<cutoff){redisClient.del(key);}}});});});}
  1. 敏感信息处理
// 不要在 session 中存储敏感信息// 错误示例:req.session.passwordHash=user.passwordHash;// ❌ 危险!// 正确做法:req.session.userId=user.id;// ✅ 只存储标识符req.session.roles=user.roles;// ✅ 存储非敏感信息// 敏感信息从数据库实时获取functiongetUserData(req,res,next){if(req.session.userId){User.findById(req.session.userId).select('-password -salt')// 排除敏感字段.then(user=>{req.user=user;next();});}else{next();}}

六、常见问题排查

  1. Session 不持久
// 检查点: // 1. Cookie 是否设置正确 console.log('Cookie:', req.headers.cookie); // 2. Session 是否保存成功 req.session.test = 'value'; req.session.save((err) => { if (err) console.error('保存失败:', err); }); // 3. 检查存储连接 redisClient.ping((err, result) => { console.log('Redis 连接:', err ? '失败' : '正常'); }); 2. 跨域问题 javascript // 前端需要设置 withCredentials fetch('/api/data', { credentials: 'include' // 包含 Cookie }); // 后端需要设置 CORS app.use(cors({ origin: 'https://frontend.com', credentials: true, // 允许凭证 methods: ['GET', 'POST', 'PUT', 'DELETE'] }));

总结

Cookie-Session 机制的核心是:

Cookie 只存 ID:安全、小巧、自动传输

Session 存数据:完整、安全、服务器可控

通过 ID 关联:建立客户端和服务器的信任桥梁

配合安全策略:HttpOnly、Secure、SameSite 多重防护

这种机制在传统 Web 应用中表现优秀,特别是需要服务器完全控制会话、需要存储大量临时数据、或需要支持服务器主动登出的场景。但在分布式系统和前后端分离架构中,需要考虑 Session 共享和扩展性问题

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 19:14:40

01-NET10简介与环境搭建

一、什么是 .NET .NET 是微软开发的一个免费、开源、跨平台的开发框架。你可以用它来开发各种应用程序。 想象一下&#xff0c;你要盖一栋房子。你需要砖头、水泥这些原材料&#xff0c;需要图纸告诉你怎么盖&#xff0c;还需要锤子、铲子等工具。在编程世界里&#xff0c;.NET…

作者头像 李华
网站建设 2026/4/17 22:26:48

时间序列中因果推断

Causal inference for time series 发表于《Nature Reviews Earth and Environment》&#xff0c;由Jakob Runge 等人撰写。文章系统梳理了时间序列因果推断的理论、方法及其在地球系统科学中的应用&#xff0c;尤其关注非线性、高维、复杂系统中的因果识别问题。以下是对该文…

作者头像 李华
网站建设 2026/4/19 12:23:33

Java 反射详解

1. 反射概述 1.1 什么是反射 反射&#xff08;Reflection&#xff09;是 Java 提供的强大特性&#xff0c;允许程序在运行时动态地获取、访问类的所有信息&#xff08;包括类名、属性、方法、构造器、注解等&#xff09;&#xff0c;并能动态操作这些信息&#xff0c;突破编译…

作者头像 李华
网站建设 2026/4/18 11:52:49

长云科技机动绞磨

在电力线路架设、野外施工抢修等场景中&#xff0c;一套不受电网束缚、能随时提供强劲牵引与提升动力的装备&#xff0c;是决定工程能否顺利推进的关键。机动绞磨机&#xff0c;正是为扮演这一“全天候动力核心”的角色而生。其价值不仅在于替代人力&#xff0c;更在于将复杂、…

作者头像 李华
网站建设 2026/4/19 1:36:32

开题卡住了?千笔ai写作,本科生论文救星!

开题卡住了&#xff1f;千笔ai写作&#xff0c;本科生论文救星&#xff01;你是否曾为论文开题绞尽脑汁&#xff1f;是否曾在深夜面对空白文档文思枯竭&#xff1f;是否反复修改却总对表达不满意&#xff1f;如果你正在经历这些学术写作的经典困境&#xff0c;那么&#xff0c;…

作者头像 李华