从Session到JWT:Spring Security认证授权演进与实战对比
最近在整理技术文档时突然想到一个有趣的比喻有时候一个看似简单的技术栈或框架一旦深入进去可能会像进入一个复杂的“丛林”当你终于掌握它并“走出来”时却发现外面的技术世界早已天翻地覆。这让我想起了很多开发者面对老旧系统、遗留代码或某个特定技术深坑时的状态。今天我们就以这个比喻为引来系统性地探讨一个经典且容易让人“陷入”的技术场景——基于传统 Session 的 Web 应用身份认证与授权并对比“走出来”后看到的现代技术世界无状态、分布式的 JWT 与 OAuth2 体系。本文将带你完整走一遍从“被困”到“走出”的全过程。如果你是刚接触 Web 安全的新手可以理解整套认证授权的演进脉络如果你正在维护老系统可以对照排查其中的安全隐患与性能瓶颈如果你想在新项目中采用现代方案本文将提供从原理到 Spring Security 实战的完整指南。1. 背景与核心概念从“丛林”到“新世界”在 Web 开发早期应用大多是单体架构用户通过浏览器登录后服务器会在内存中创建一个Session对象来存储用户状态如用户ID、角色。这个 Session 有一个唯一的 IDSession ID通过 Cookie 传递给浏览器。此后浏览器的每次请求都会带上这个 Cookie服务器通过 Session ID 找到对应的 Session从而识别用户。这就像那盘“桌游”规则简单登录-存Session-校验Session自成一派在单服务器、小规模用户时运行良好。但它的“丛林”陷阱也随之而来状态存储Session 存储在服务器内存或 Redis服务器是有状态的。扩展性差当需要部署多台服务器做负载均衡时Session 需要共享否则用户可能被分配到没有其 Session 的服务器导致重复登录。CSRF 攻击基于 Cookie 的机制容易受到跨站请求伪造攻击。移动端不友好原生 App 处理 Cookie 不如浏览器自然。而当技术世界演进到微服务、前后端分离、跨域应用的时代时无状态Stateless认证成为了“新世界”的通行证。其核心代表是JWT (JSON Web Token)和OAuth 2.0授权框架。服务器不再保存会话状态而是将认证信息加密后放入 Token发给客户端。客户端后续请求在 Header 中携带此 Token服务器只需验证 Token 的有效性和签名即可。2. 环境准备与版本说明为了清晰地对比两种模式我们将创建一个 Spring Boot 项目分别实现传统的 Session 认证和现代的 JWT 认证。环境要求操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)JavaJDK 11 或 17 (推荐 17)构建工具Maven 3.6 或 Gradle 7.xIDEIntelliJ IDEA, VS Code 或 Eclipse数据库H2 Database (内存数据库便于演示) 或 MySQL 8.0项目初始化使用 Spring Initializr 创建项目选择以下依赖Spring WebSpring SecuritySpring Data JPAH2 Database (或 MySQL Driver)Lombok (简化代码)生成的pom.xml关键依赖如下Maven 示例?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version !-- 使用一个稳定的版本 -- relativePath/ /parent groupIdcom.example/groupId artifactIdauth-demo/artifactId version0.0.1-SNAPSHOT/version nameauth-demo/name descriptionDemo project for Session vs JWT Auth/description properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- JWT 相关依赖用于后半部分 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency dependency groupIdorg.springframework.security/groupId artifactIdspring-security-test/artifactId scopetest/scope /dependency /dependencies !-- 省略 build 和 plugin 配置 -- /project项目结构预览src/main/java/com/example/authdemo/ ├── config/ # 安全配置类 ├── controller/ # 控制器 ├── dto/ # 数据传输对象 ├── entity/ # JPA 实体 ├── repository/ # 数据访问层 ├── service/ # 业务逻辑层 └── util/ # 工具类如JWT3. “丛林”深处传统 Session 认证实战与原理拆解我们先实现一个最经典的 Session 认证流程理解其运作机制和固有局限。3.1 核心原理与流程sequenceDiagram participant C as Client (Browser) participant S as Server participant D as Session Store (Memory/Redis) C-S: 1. POST /login (username, password) S-S: 2. 验证凭证 S-D: 3. 创建Session对象存储用户信息 D--S: 4. 生成唯一Session ID S--C: 5. 响应Set-Cookie: JSESSIONIDxxx C-S: 6. 后续请求自动携带Cookie S-D: 7. 根据Session ID查找Session D--S: 8. 返回用户信息 S--C: 9. 处理业务返回响应登录客户端提交凭证。验证与创建会话服务器验证成功在服务端存储会话数据内存、数据库、Redis生成唯一 Session ID。下发凭证通过Set-Cookie头将 Session ID 写入浏览器 Cookie。携带凭证浏览器后续对同域请求自动携带此 Cookie。验证会话服务器从 Cookie 中取出 Session ID查找对应的会话数据完成身份识别。3.2 实战Spring Security 默认 Session 配置Spring Security 默认就启用了 Session 管理。我们创建一个简单的用户和接口来观察。实体与存储// file: src/main/java/com/example/authdemo/entity/User.java package com.example.authdemo.entity; import lombok.Data; import javax.persistence.*; Entity Data Table(name users) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(unique true, nullable false) private String username; Column(nullable false) private String password; private String role; // 例如USER, ADMIN }// file: src/main/java/com/example/authdemo/repository/UserRepository.java package com.example.authdemo.repository; import com.example.authdemo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepositoryUser, Long { OptionalUser findByUsername(String username); }安全配置默认Session// file: src/main/java/com/example/authdemo/config/SecurityConfig.java package com.example.authdemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .antMatchers(/public/**).permitAll() .antMatchers(/admin/**).hasRole(ADMIN) .anyRequest().authenticated() // 其他所有请求都需要认证 ) .formLogin(form - form .loginPage(/login) // 自定义登录页默认是 /login .permitAll() ) .logout(logout - logout .permitAll() ) // 默认启用 Session 管理CSRF 保护也是开启的 .csrf().disable(); // 为了方便测试API先禁用CSRF生产环境慎用 return http.build(); } Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 需要实现 UserDetailsService这里简化为返回内存用户 // 实际项目应从数据库加载 }测试控制器// file: src/main/java/com/example/authdemo/controller/TestController.java package com.example.authdemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; RestController RequestMapping(/api) public class TestController { GetMapping(/session-info) public String getSessionInfo(HttpSession session) { return Session ID: session.getId() , Creation Time: session.getCreationTime() , Last Accessed: session.getLastAccessedTime(); } GetMapping(/admin/hello) public String adminHello() { return Hello Admin!; } GetMapping(/user/hello) public String userHello() { return Hello User!; } }运行与验证启动应用访问http://localhost:8080/login(Spring Security 默认提供简单登录页)。使用配置的用户名密码登录需要先实现UserDetailsService或使用内存用户。登录成功后访问/api/session-info可以看到服务器返回的 Session 信息。同时检查浏览器开发者工具的Application-Cookies会发现一个名为JSESSIONID的 Cookie。访问/api/admin/hello或/api/user/hello请求会自动携带JSESSIONIDCookie服务器通过它识别用户身份和权限。3.3 “丛林”的困境Session 的局限性扩展性问题Session 共享多实例部署时必须将 Session 存储到外部如 Redis。这增加了架构复杂度和网络开销。# application.yml 配置 Redis 存储 Session spring: session: store-type: redis redis: host: localhost port: 6379CSRF 攻击虽然 Spring Security 默认提供了 CSRF 防护需要配合表单提交的_csrftoken但对于纯 API 项目管理起来比较麻烦通常选择禁用这又带来了安全风险。移动端/跨域不友好原生 App 需要手动管理 Cookie。在前后端分离且跨域的场景下Cookie 的处理需要额外配置CORS。服务器内存压力用户量巨大时存储所有用户的 Session 对服务器内存是巨大挑战。4. 走出“丛林”JWT 无状态认证实战JWT 是“新世界”的轻量级凭证。它是一串加密的字符串由三部分组成Header头部、Payload负载、Signature签名。服务器签发后客户端保存通常放在localStorage或Authorization头后续请求时携带服务器只需验证签名即可无需查询会话存储。4.1 JWT 结构解析一个 JWT 看起来像这样xxxxx.yyyyy.zzzzzHeader声明类型和加密算法如{alg: HS256, typ: JWT}Base64Url 编码。Payload存放实际传递的数据Claims如用户ID、角色、过期时间Base64Url 编码。注意Payload 只是编码并非加密不要存放敏感信息。Signature对前两部分的签名防止数据篡改。HMACSHA256(base64UrlEncode(header) . base64UrlEncode(payload), secret)4.2 实战集成 JWT 到 Spring Security我们将改造上面的项目用 JWT 替代 Session。第一步创建 JWT 工具类// file: src/main/java/com/example/authdemo/util/JwtUtil.java package com.example.authdemo.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; Component public class JwtUtil { Value(${jwt.secret}) // 从配置读取密钥 private String secret; Value(${jwt.expiration}) // 过期时间(毫秒) private Long expiration; private Key key; PostConstruct public void init() { // 将字符串密钥转换为安全的 Key 对象 this.key Keys.hmacShaKeyFor(secret.getBytes()); } // 从token中获取用户名 public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } // 从token中获取过期时间 public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public T T extractClaim(String token, FunctionClaims, T claimsResolver) { final Claims claims extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } // 检查token是否过期 private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } // 为用户生成token public String generateToken(UserDetails userDetails) { MapString, Object claims new HashMap(); // 可以将角色等信息放入claims claims.put(roles, userDetails.getAuthorities()); return createToken(claims, userDetails.getUsername()); } private String createToken(MapString, Object claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() expiration)) .signWith(key, SignatureAlgorithm.HS256) .compact(); } // 验证token public Boolean validateToken(String token, UserDetails userDetails) { final String username extractUsername(token); return (username.equals(userDetails.getUsername()) !isTokenExpired(token)); } }第二步创建 JWT 认证过滤器这个过滤器会拦截请求从Authorization头中提取 JWT并进行验证。// file: src/main/java/com/example/authdemo/filter/JwtAuthenticationFilter.java package com.example.authdemo.filter; import com.example.authdemo.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Autowired private JwtUtil jwtUtil; Autowired private UserDetailsService userDetailsService; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader request.getHeader(Authorization); String username null; String jwt null; // 检查请求头是否包含Bearer Token if (authorizationHeader ! null authorizationHeader.startsWith(Bearer )) { jwt authorizationHeader.substring(7); // 去掉Bearer username jwtUtil.extractUsername(jwt); } // 如果用户名不为空且当前SecurityContext中没有认证信息 if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { UserDetails userDetails this.userDetailsService.loadUserByUsername(username); // 验证token有效性 if (jwtUtil.validateToken(jwt, userDetails)) { // 创建认证令牌并设置到SecurityContext中 UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } chain.doFilter(request, response); } }第三步修改安全配置集成 JWT 过滤器并禁用 Session// file: src/main/java/com/example/authdemo/config/JwtSecurityConfig.java package com.example.authdemo.config; import com.example.authdemo.filter.JwtAuthenticationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; Configuration EnableWebSecurity public class JwtSecurityConfig { Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() // JWT无需CSRF保护 .authorizeHttpRequests(authz - authz .antMatchers(/api/auth/**).permitAll() // 认证接口放行 .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) // 关键配置设置Session创建策略为STATELESS无状态 .sessionManagement(session - session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) // 在UsernamePasswordAuthenticationFilter之前添加我们的JWT过滤器 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 暴露AuthenticationManager Bean用于登录认证 Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } }第四步创建认证接口登录// file: src/main/java/com/example/authdemo/controller/AuthController.java package com.example.authdemo.controller; import com.example.authdemo.dto.LoginRequest; import com.example.authdemo.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/auth) public class AuthController { Autowired private AuthenticationManager authenticationManager; Autowired private JwtUtil jwtUtil; // 假设有一个自定义的 UserDetailsService 实现这里省略 PostMapping(/login) public ResponseEntity? login(RequestBody LoginRequest loginRequest) { // 1. 使用Spring Security的AuthenticationManager进行认证 Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); // 2. 认证成功将认证信息设置到上下文可选JWT过滤器会处理 // SecurityContextHolder.getContext().setAuthentication(authentication); // 3. 生成JWT Token final UserDetails userDetails (UserDetails) authentication.getPrincipal(); final String jwt jwtUtil.generateToken(userDetails); // 4. 返回Token return ResponseEntity.ok(new AuthResponse(jwt)); } // 简单的DTO static class AuthResponse { private final String token; public AuthResponse(String token) { this.token token; } public String getToken() { return token; } } } // file: src/main/java/com/example/authdemo/dto/LoginRequest.java package com.example.authdemo.dto; import lombok.Data; Data public class LoginRequest { private String username; private String password; }第五步配置文件# file: src/main/resources/application.yml spring: datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: update show-sql: true h2: console: enabled: true # 启用H2控制台访问 /h2-console jwt: secret: mySecretKeyWhichIsVeryLongAndSecureAtLeast32BytesLong! # 密钥必须足够长且复杂 expiration: 86400000 # Token有效期单位毫秒 (24小时)运行与验证使用 JWT启动应用。使用 Postman 或 curl 调用登录接口curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:your_password}响应应包含一个 JWT Token{token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...}使用获取到的 Token 访问受保护接口curl -X GET http://localhost:8080/api/admin/hello \ -H Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...应该成功收到Hello Admin!的响应。服务器不再依赖 Session仅靠 Token 即可完成认证。5. 常见问题与排查思路在从 Session 迁移到 JWT 或无状态架构的过程中你会遇到各种问题。下表列出了一些典型问题及解决思路问题现象可能原因排查步骤与解决方案登录成功但后续接口返回 403/4011. Token 未正确放入请求头。2. Token 已过期。3. Token 签名验证失败密钥不一致。4. 过滤器配置顺序错误JWT过滤器未生效。1. 检查请求头是否为Authorization: Bearer token。2. 检查 Token 过期时间使用 jwt.io 解码查看exp字段。3. 确认签发和验证使用的是同一个 Secret 密钥。4. 检查SecurityFilterChain配置确保JwtAuthenticationFilter被添加在UsernamePasswordAuthenticationFilter之前。多服务实例下登录后访问另一服务失败Session 未共享。Session方案配置 Spring Session 并集成 Redis 等外部存储。JWT方案确保所有服务实例使用相同的 JWT Secret 密钥进行签名验证。密钥需安全统一管理。Token 被盗用怎么办JWT 一旦签发在有效期内无法主动使其失效。1.设置较短的过期时间如 15-30 分钟配合 Refresh Token 机制。2.使用 Token 黑名单当用户注销或修改密码时将未过期的 Token 加入黑名单存于 Redis验证时检查黑名单。这会引入一点状态但提升了安全性。3.精细化 ClaimsToken 中只存必要信息如用户ID角色信息可从数据库实时查询。如何实现退出登录无状态架构下服务器没有 Session 可销毁。1.客户端主动丢弃 Token前端删除存储的 Token。2.服务端维护黑名单如上所述。3.使用短有效期 Token等待其自然过期。跨域 (CORS) 请求携带 Cookie 或自定义头失败浏览器安全策略限制。在 Spring Boot 后端配置 CORS明确允许前端域名、凭证Cookie和自定义头如Authorization。JWT Payload 数据过多导致 Token 过长在 Payload 中存储了过多用户信息。Payload 只应存放标识用户身份的最小信息如 userId。其他详细信息应在验证 Token 后从数据库查询。HTTP 头部大小有限制过长的 Token 可能被服务器或代理拒绝。6. 最佳实践与工程建议掌握了基础实现后要在生产环境中安全、高效地使用还需要遵循以下最佳实践密钥安全管理绝对不要将 JWT Secret 硬编码在代码中或提交到版本库。使用环境变量、配置中心如 Apollo、Nacos或云服务商的安全密钥管理服务来存储密钥。密钥长度至少 32 字节并使用安全的随机生成器。Token 生命周期管理采用 Access Token Refresh Token 双 Token 机制。Access Token短期有效如 15-30 分钟用于业务 API 调用。Refresh Token长期有效如 7 天存储于安全的 HttpOnly Cookie 或服务端仅用于获取新的 Access Token。这样即使 Access Token 泄露影响时间也较短。Refresh Token 的刷新和撤销逻辑需要精心设计。避免在 Payload 中存储敏感信息JWT 的 Payload 仅是 Base64 编码任何人拿到 Token 都可以解码看到内容。切勿存放密码、手机号等敏感信息。选择合适的签名算法HS256 (HMAC with SHA-256)对称加密服务端用一个密钥签名和验证。性能好但密钥分发和管理需谨慎。RS256 (RSA Signature with SHA-256)非对称加密服务端用私钥签名客户端或用其他服务用公钥验证。更安全适合微服务间认证。做好日志与监控记录 Token 的签发、验证失败、过期刷新等关键事件。监控异常登录行为如短时间内同一用户大量获取 Token。前端安全存储对于 SPA 应用将 Access Token 存储在内存或sessionStorage中页面关闭即失效。避免存储在localStorage以防 XSS 攻击窃取。如果必须存请确保有完善的 XSS 防御。Refresh Token 应通过安全的、HttpOnly的 Cookie 发送防止 JavaScript 访问。API 网关统一认证在微服务架构中可以在 API 网关层统一进行 JWT 的验证和路由避免每个微服务重复实现认证逻辑。从“被困在 Session 丛林”到“掌握 JWT 新世界”本质是从有状态到无状态、从中心化存储到去中心化验证的思想转变。这种转变带来了巨大的扩展性和灵活性适合现代分布式应用。然而没有银弹JWT 也引入了令牌管理、即时失效等新挑战。理解两者的原理、实现和优劣才能根据你的实际业务场景如企业内部系统、高并发互联网应用、微服务集群做出最合适的技术选型。建议从本文的示例项目出发逐步尝试双 Token 机制、集成 Redis 黑名单、尝试 RS256 算法最终构建出一套健壮、安全的身份认证体系。

相关新闻

OpenClaw爆火背后:AI智能体如何实现开箱即用与本地化部署

OpenClaw爆火背后:AI智能体如何实现开箱即用与本地化部署

1. 从“小龙虾”到现象级AI智能体:OpenClaw为何一夜爆火?最近,我的技术圈和产品圈的朋友们,几乎都在讨论同一个名字:OpenClaw。如果你还没听说过,可能会有点懵,这听起来像是个海鲜品牌或者游戏角…

2026/8/6 13:30:31 阅读更多 →
Wand-Enhancer:如何通过开源扩展优化游戏修改体验

Wand-Enhancer:如何通过开源扩展优化游戏修改体验

Wand-Enhancer:如何通过开源扩展优化游戏修改体验 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand-Enhancer 是一个专注于提升 Wan…

2026/8/6 13:30:31 阅读更多 →
GEO收录下降的六大原因,星琛科技教你如何精准排查与优化

GEO收录下降的六大原因,星琛科技教你如何精准排查与优化

引言“为什么我的文章在百度上收录了,但在文心一言、Kimi、ChatGPT里却搜不到?”“之前GEO排名挺好的,最近怎么突然下降了?”——这是许多尝试GEO优化的企业,尤其是中小型企业,最常遇到的困惑。GEO&#xf…

2026/8/6 13:30:31 阅读更多 →

最新新闻

Selenium自动化测试:操作元素对象

Selenium自动化测试:操作元素对象

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快一、元素的常用操作element.click() # 单击元素;除隐藏元素外,所有元素都可单击element.submit() # 提交表单;可通过form表单元素…

2026/8/6 14:25:02 阅读更多 →
Zygisk-Assistant深度解析:Android Root隐藏技术的演进与实战指南

Zygisk-Assistant深度解析:Android Root隐藏技术的演进与实战指南

Zygisk-Assistant深度解析:Android Root隐藏技术的演进与实战指南 【免费下载链接】Zygisk-Assistant A Zygisk module to hide root for KernelSU, Magisk and APatch, designed to work on Android 5.0 and above. 项目地址: https://gitcode.com/gh_mirrors/zy…

2026/8/6 14:25:02 阅读更多 →
从零制作东方Project角色模型:Blender与MMD工作流全解析

从零制作东方Project角色模型:Blender与MMD工作流全解析

如果你在B站、YouTube或各类游戏社区关注过东方Project的二创内容,可能会发现一个有趣的现象:那些最让人印象深刻的角色模型或动画,往往并非出自官方,而是由爱好者们用各种“野生”技术栈一点点“捏”出来的。最近,一个…

2026/8/6 14:25:02 阅读更多 →
从GX Works2报错到系统优化:深入理解存储器分类与层次结构

从GX Works2报错到系统优化:深入理解存储器分类与层次结构

1. 从“空间不足”的报错说起:为什么我们需要了解存储器分类? 最近在工控圈子里,看到不少朋友在讨论三菱GX Works2编程软件报出的“存储器空间或桌面堆栈不足”这个经典错误。这个弹窗一出来,往往意味着项目编译失败,程…

2026/8/6 14:25:02 阅读更多 →
谷歌 AI 大将集体出走,联手创办 Discovery Loop

谷歌 AI 大将集体出走,联手创办 Discovery Loop

据《WIRED》报道,谷歌顶尖 AI 科学家杰夫迪恩与多位高管离职,共同创办 Discovery Loop,探索用 AI 驱动药物研发、芯片设计等领域的科学突破。这位谷歌大脑联合创始人的出走,被视为 AI 人才向创业公司迁徙的重要信号。 一、杰夫迪恩…

2026/8/6 14:25:02 阅读更多 →
基于LVGL的嵌入式圆形表盘UI开发:从资源优化到时间显示实战

基于LVGL的嵌入式圆形表盘UI开发:从资源优化到时间显示实战

1. 项目概述:从零构建一个LVGL圆形表盘UI最近在折腾一个基于STM32和ESP32-S3的智能穿戴设备原型,核心需求是在一块圆形的OLED或LCD屏幕上显示一个美观且信息丰富的表盘界面。这听起来像是智能手表的基础功能,但当你真正动手时,会发…

2026/8/6 14:24:02 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/5 15:00:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/5 13:13:56 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/5 10:20:36 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/5 21:00:14 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →