惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
罗磊的独立博客
S
SegmentFault 最新的问题
V
V2EX
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
D
Docker
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
M
Microsoft Research Blog - Microsoft Research
Martin Fowler
Martin Fowler
S
Secure Thoughts
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
True Tiger Recordings
GbyAI
GbyAI
P
Proofpoint News Feed
P
Privacy International News Feed
Jina AI
Jina AI
The Cloudflare Blog
I
Intezer
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Archives - TechRepublic
NISL@THU
NISL@THU
The Register - Security
The Register - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
S
Schneier on Security
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA

DEV Community

Why AI Should Not Write SQL Against ERP Databases Vibe coding works until it doesn't. The debt is real. Shipping at the Edge: Migrating a Coffee Subscription Platform to Cloudflare Workers Stop Tab-Switching: A Developer's Guide to Color Tools That Actually Fit the Workflow DevOps vs MLOps vs AIOps: What Changes, What Stays, and a Simple Roadmap to Get Started Run Powerful AI Coding Locally on a Normal Laptop 5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week) What I Learned Building My Own AI Harness Hytale Servers Will Fail Treasure Hunts Until We Fix Our Event Handling Redux in React: Managing Global State Like a Pro Unfreezing Your GitHub Actions: Troubleshooting Stuck Deployments and Protecting Your Git Repo Statistics Unlocking Project Discoverability on GHES: A Key to Software Engineering Productivity When the Cleanup Code Becomes the Project Rockpack 8.0 - A React Scaffolder Built for the Age of AI-Assisted Development Mismanaging the Treasure Hunt Engine in Hytale Servers Will Get You Killed Why Hardcoded Automations Fail AI Agents Stop Calling It an AI Assistant. It’s Already Managing Your Company Why I built a post-quantum signing API (and why JWT is on borrowed time) Weekend Thought: Frontend Build Tools Suffer From Work Amnesia AI Is Changing Engineering Culture More Than We Realize A 10-Line Playwright Trick That Saved Me Hours on Every Sephora Run Everyone Was Focused on Gemini, But Infinite Scaler Was the Real Twister "Gemma 4 Analyzed My Bank Statements – Apparently I 'Have a Problem' with Coffee and Late-Night Apps" #css #webdev #beginners #codenewbie The Hidden Layer Every AI Developer Must Learn AlphaEvolve: Google DeepMind's Gemini-Powered Evolutionary Coding Agent RDS Reserved Instance Pricing: Every Engine, Every Rule, Real Dollar Savings How To Build An AI-Powered MVP Without Burning Your Startup Budget In 2026 Reading a Psychrometric Chart Without Getting Lost LMR-BENCH: Can LLM Agents Reproduce NLP Research Code? (EMNLP 2025) How to turn text into colors (without AI) Building Real-Time Apps in Node.js with Rivalis: WebSockets, Rooms, Actors, and a Binary Wire This Week In React #282 : Security, Fate, TanStack, Redux, Jotai | Hermes-node, Expo, Rozenite, Harness | TC39, Bun, pnpm, npm, Yarn, Node AI Copilot vs AI Agent Architecture - What's Actually Different (And Why It Matters) Smart Contract Security: NEAR's Futures Surge and AI Token Risks Database Maintenance: Tracing Production Incidents to Their Root Cause Stop juggling AI SDKs in PHP — meet Prisma Google Quietly Changed What “Apps” Mean at I/O 2026 The Infrastructure Team Is the Real Single Point of Failure Building SQLite from Scratch: 740 Lines of C++23 to Understand Every Byte of a .db File The 4 Levels of Hermes Agent Scaling Framework: From One Hermes Agent to a Fully Automated Team Your AI Has a Memory. It Just Doesn’t Know What to Remember. Claprec: Engineering Tradeoffs - Limited time vs. Perfection (6/6) Building a Daily Google News API Monitor in Python Building RookDuel Avikal: From Chess Steganography to Post-Quantum Archival Security Google I/O e IA: o que realmente muda na vida do dev? Color Contrast Failures: The Number One Accessibility Issue and How to Fix It # I Watched 15 Hours of Hermes Agent Videos So You Don't Have To Cómo solucionar el bucle infinito en useEffect con objetos y arrays en React The First Agent-Centric Cloud Security Platform — And Why We Didn't Build It That Way On Purpose
JWT与Session Token在Spring Boot中的对比:资深开发者的决策指南
Davide Mibel · 2026-05-21 · via DEV Community

三年前,每当我有人问及Spring Boot中的身份验证时,我都会给出同样的答案:“使用JWT,它是无状态的,它可以扩展。”我一半是对的,一半是错的,直到我继承了两个生产代码库——其中一个以一种非常具体的方式损坏了——我才明白哪一半是对的,哪一半是错的。

这不是关于如何实现其中任何一个的教程。这是我希望在我开始默认推荐JWT之前就拥有的决策指南。

真正教你什么教程

大多数 Spring Boot 安全教程会带你了解 JWT,因为它能让演示更简洁。你添加一个过滤器,验证一个签名,设置 SecurityContext,搞定。没有数据库调用,没有共享状态,天生无状态。感觉架构上很清晰。

他们很少展示:当用户更改密码时会发生什么。或者他们的账户被暂停。或者在一个设备上登出,并期望这意味着所有设备都会发生什么。在一个纯 JWT 设置且没有黑名单的情况下,这三个问题的答案是“直到令牌过期什么都不会发生。”

另一方面,会话感觉过时了。“那不扩展。” “你需要粘性会话。” 这两者都不再正确,我会向你展示为什么。

Spring Boot 中会话的实际工作原理

Spring Session with Redis 只需要三个注解和一个依赖:

@EnableRedisHttpSession
@Configuration
public class SessionConfig {
    // Spring Session handles everything else
}

进入全屏模式 退出全屏模式

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

进入全屏模式 退出全屏模式

客户端获取一个不可见的会话 ID(通常是 32 个十六进制字符),该 ID 存储在HttpOnly; Secure cookie。每个请求都会发送这个 cookie,Spring 在 Redis 中查找会话,反序列化它,并填充到 SecurityContext 中。会话数据存储在 Redis 中,而不是 token 本身.

撤销是 sessionRepository.deleteById(sessionId)。即时生效,没有例外.

水平扩展开箱即用——每个实例都连接到同一个 Redis。不需要粘性会话。这已经不是 2009 年了。

Side-by-side flow comparison — Session flow: client cookie to server to Redis lookup to SecurityContext; JWT flow: client Bearer token to server to local signature verify to SecurityContext. Annotate the revocation point on sessions (delete from Redis) and the revocation gap on JWT (no early invalidation without blocklist)

JWT:你实际获得的内容

JWT 是一个经过 base64 编码的 JSON 有效载荷(声明),使用密钥或私钥进行签名。服务器不会存储它。通过检查签名进行本地验证——无需数据库调用,无需网络跳转。

这在两种特定情况下很重要:

独立验证令牌的微服务。 如果你拥有五个服务并且每个服务都需要知道调用者是谁,那么会话就需要每个服务调用共享存储或中央认证服务。JWT 允许每个服务仅使用公钥或共享密钥在本地验证令牌。

第三方和移动客户端。 浏览器自动处理 Cookie。原生应用和第三方 API 客户端则不处理。JWT 在Authorization 标头无需cookie配置即可在任何地方工作.

在这两种情况之外,你支付了JWT的成本,却没有获得JWT的好处.

这些成本是真实的:

  • 没有黑名单就无法撤销。 15分钟的访问令牌不能提前失效。被盗的令牌在过期之前一直有效。如果你在每次请求中都添加一个 Redis 阻止列表进行检查,你就只是重新添加了你试图避免的数据库调用。
  • 令牌大小。 会话 Cookie 是 32 字节。一个包含少量声明的 JWT 在每次请求头中占用 300–600 字节,永远如此。在高频内部 API 中,这会累积起来。
  • 实现表面. JWT有着长期的安全漏洞历史:alg: none攻击、弱的HMAC密钥、缺少过期验证、错误的观众检查。Spring Security正确处理了大部分这些问题,但复杂性预算高于会话.

真正的决策框架

停止询问"哪个更好?"并询问"我实际上需要什么?"

使用会话时:

  • 您控制前端——一个基于浏览器的应用程序,使用您自己的后端
  • 您需要立即撤销:注销意味着注销,密码更改意味着所有会话都失效
  • 您正在构建一个单体应用程序或一个小型服务,该服务拥有自己的身份验证
  • 您已经使用 Redis 进行缓存或队列操作

使用 JWT 时:

  • 多个独立服务需要在不调用中央存储的情况下验证身份
  • 您有非浏览器客户端——移动应用、命令行工具、第三方集成——这些客户端难以处理 Cookie
  • 您需要联合身份验证:由外部身份提供者(Auth0、Keycloak、Cognito)发布的令牌,您的服务进行验证

不要使用 JWT,因为:

  • "它是无状态的,并且可以扩展" — Redis会话在任何数量的实例之间扩展得一样好
  • "每个人都用它" — 盲目模仿安全决策就是你会得到在生产环境中不可撤销的7天令牌的原因

谁都不谈论的混合设置

我所见过的许多生产系统在这方面的优秀实践,都是结合使用:浏览器前端使用会话,服务间调用和移动客户端使用 JWT。

Spring Security 通过多个 SecurityFilterChain 组件干净地支持这一点:

@Bean
@Order(2)
public SecurityFilterChain jwtFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/v1/**")
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .csrf(AbstractHttpConfigurer::disable)
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
    return http.build();
}

@Bean
@Order(1)
public SecurityFilterChain sessionFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/web/**")
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
        .formLogin(Customizer.withDefaults())
        .logout(logout -> logout.logoutSuccessUrl("/web/login?logout"))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/web/login").permitAll()
            .anyRequest().authenticated());
    return http.build();
}

进入全屏模式 退出全屏模式

两条链,不同的匹配器,不同的策略。浏览器应用获取会话和完整撤销。API和服务调用获取JWT。Spring按@Order顺序应用它们——第一个匹配的链获胜.

实际的性能差异看起来是怎样的

会话每次请求增加一个 Redis 循环往返时间——在配置良好的本地 Redis 上,通常为 0.5–2ms,如果 Redis 位于单独的可用区,则为 2–5ms。对于处理时间已经需要 50–200ms 的请求来说,这是微不足道的。

JWT 验证是在内存中:解析 base64,验证 HMAC 签名,检查过期时间。亚毫秒级。如果你正在构建一个需要每秒处理数千次请求且预算为微秒级的 API,这种差异很重要。如果你正在构建一个标准的 Web 应用程序或业务 API,则没有关系.

令牌大小的差异在总体上比你预期的更重要。会话 Cookie 是SESSION=<32-hex-chars> — 在 Cookie 头部大约有 50 字节。JWT 是 Authorization: Bearer <base64> — 在每个请求中,Authorization 头部通常为 400–700 字节。在一个有 10,000 个活跃用户、每小时每个用户发起 20 次请求的应用中,仅头部开销就大约是会话的 14 倍(会话为 5MB,头部为 70MB/小时)。在内部高频调用的微服务 API 中,这会累积成实际成本。

这两者本身都不是选择其中一方的理由。它们是需要与架构适配性权衡的因素,而不是让你做决定的论据。

JWT 容易犯的安全错误

如果你正确使用 Spring Security,它可以帮助你避免许多 JWT 的陷阱,但我见过所有这些在生产代码库中:

对称密钥太短。 HS256 需要 256 位(32 字节)的密钥。短密钥可以通过暴力破解。使用 openssl rand -base64 32 生成它,并将其存储在你的密钥管理器中,而不是 application.yml 中。

不进行观众或发行者验证。 如果你有多個服务接受相同的 JWT,为服务 A 签发的令牌可能会被重放攻击服务 B,除非你验证 audiss 声称。Spring Security 的 JwtDecoder 支持 .claimValidator("aud", ...)

记录令牌。 访问日志、调试语句、错误跟踪。JWT 是一种凭证。在您的日志配置中将其视为密码。

在单体应用中使用 RS256。 RS256(非对称)在多个服务需要验证单一认证服务发布的令牌时是有意义的。在一个单体应用中,只有一个服务负责发行和验证令牌,RS256会增加密钥管理的复杂性,而没有任何安全优势。HS256是正确的默认选项.

我最常看到的错误

团队最初使用JWT是因为一个教程推荐了它。六个月后,他们需要实现“从所有设备登出”或“密码更改后强制重新认证”。在那个时刻,他们添加了一个Redis黑名单来在过期前使令牌失效——这意味着现在每个JWT验证都会在每个请求中访问Redis。他们保留了所有JWT的复杂性,并在顶部添加了会话存储。

我已经做完了。结果代码比纯会话或纯JWT更难理解。

如果你需要撤销功能,请使用会话。如果你确实需要无状态的跨服务验证,请使用JWT,并有意设计撤销限制——15分钟的访问令牌,刷新令牌轮换,在发货前制定明确的会话失效策略——而不是六个月后当作事后想法。

application.yml showing spring.session.store-type=redis, spring.session.timeout=30m, server.servlet.session.cookie.http-only=true, server.servlet.session.cookie.secure=true — the minimal Spring Session Redis config needed to replace an overly complex JWT setup

无聊的答案往往是对的。Spring Session with Redis 自 2015 年起就正确地解决了这个问题。JWT 解决了一个特定的分布式系统问题。在做出选择之前,先了解你实际上面临的是哪种问题。

你当前的设置使用的是什么,这是一个深思熟虑的选择还是来自教程?


最初发布于 Medium