☰
JWT登录认证实战:Token生命周期、拦截器与ThreadLocal全解析
2026/10/2 5:04:40 网站建设 项目流程

做了几年Java后端,我越来越觉得,很多项目卡在登录认证这一关,不是不会写接口,而是没把 token 的生命周期当回事。苍穹外卖这个项目我前后完整写过两遍,第一遍跟着教程敲,第二遍自己从零重构,每次都在 JWT token 这里绕好几圈。管理端和用户端两套登录,登录后都会签发一个 JWT token,后续请求全靠这个 token 识别用户身份,拦截器统一校验,ThreadLocal 保存当前登录用户信息。整个过程非常适合拿来理解 Java Web 里最经典的登录态方案,也更适合用来排查那些“登录成功后过一会儿就失效”“换个接口就 401”之类的问题。

这篇内容我不打算讲太玄的概念,就按我实际写苍穹外卖时踩过的坑、看过的代码和最终留下的方案来聊。如果你是刚学完 Spring Boot,想找一个能落地的 token 鉴权模板;或者你已经写完苍穹外卖,但发现自己只是照着抄了 JwtUtil 和拦截器,遇到问题依然不知道怎么排查,那这篇对你有用。

1. 为什么苍穹外卖必须用 JWT 做登录认证

1.1 Cookie、Session、Token 三兄弟,先理清楚再动手

HTTP 协议天生是无状态的。用户发一次请求,服务器处理完就结束了,下一次请求来了,服务器根本不记得这个人是谁。早期的 Web 项目用 Cookie + Session 解决记忆问题:用户登录成功后,服务器在内存里存一份 session,再把 sessionId 通过 Set-Cookie 塞给浏览器,浏览器每次请求自动带上这段 Cookie,服务器拿 sessionId 去查对应的 session 对象,查到就说明用户登录过。

这套方案在单体应用里非常好用,但如果前后端分离,尤其是小程序、H5、桌面管理端并存的时候,Cookie 的自动携带就没那么友好了。小程序里发请求不一定走浏览器 Cookie 机制,管理后台如果是独立前端部署,跨域时 Cookie 的维护成本也不低。更重要的是,Session 数据存在服务器内存里,多台服务器部署时还得考虑 session 共享,要么用 Spring Session + Redis,要么做粘性会话,成本一下就上去了。

Token 方案把“存储”从服务器端转移到了客户端。登录成功后,服务器签发一个 token 字符串返回给前端,前端自己保存,以后每个请求在 Header 里显式带上这个 token。服务器不再需要保存会话记录,只需要校验 token 本身是否合法。这样做的好处是天然支持前后端分离,也方便水平扩展。JWT(JSON Web Token)是 token 方案里最流行的一种,它本身就是一串有结构的字符串,能被解析和验签,所以非常适合当苍穹外卖这种单体项目的登录凭证。

1.2 苍穹外卖两套账号体系,登录痛点到底在哪

苍穹外卖不是一套账号走天下,它有两个入口:管理端给员工用,用户端给 C 端消费者用。管理端登录走/admin/employee/login,用户端登录走/user/user/login,对应的账号表分别是员工表和用户表。

如果不用 JWT,最容易出现的局面是:每个接口都自己判断“当前是谁在操作”。比如管理端的菜品管理接口,要判断这个人是不是已经登录的员工;用户端的下单接口,要判断这个用户是不是有效用户。如果每个 Controller 都去写一段从 Cookie 或 Header 取登录态的逻辑,代码会非常臃肿,而且很容易漏。实际项目里更需要的是统一走拦截器,在请求进入 Controller 之前就把登录校验做掉。

另外,苍穹外卖的管理端和用户端是两套不同的权限体系。管理端接口路径普遍以/admin/**开头,用户端以/user/**开头。这意味着我们可以针对两套路径配置完全不同的拦截规则。如果使用 Session,通常只能靠路径去区分不同会话域,不够直观;但用 JWT,我们可以在 token 的载荷里标记用户角色和用户 id,拦截器解析后清楚知道这个 token 到底是员工 token 还是用户 token,逻辑非常干净。

1.3 选 JWT 的真正理由与它的边界

JWT 的核心优势可以概括成三个词:无状态、可验证、跨端通用。但 JWT 不是万能药,它在苍穹外卖这种单体项目里也有明显边界。

JWT 无状态意味着 token 一旦签发,在过期之前都是有效的。服务器没有办法主动把这个 token 作废。如果员工被禁用账号,只要他的 token 还没过期,他仍然可以访问接口。所以实际项目里不能只依赖 JWT,通常还需要配合账号状态校验、Redis 黑名单等方案做补偿。

另一个边界是 JWT 的载荷不要存敏感信息。JWT 的 Payload 虽然不能被篡改,但它是 Base64URL 编码的,任何拿到 token 的人都可以直接解码看到里面的内容。如果往里面塞密码、手机号、身份证号,等于把这些信息交到了客户端手里。苍穹外卖里 JWT 载荷只需要存员工 id 或用户 id 这类非敏感标识就够了,其他用户信息在查到数据库后再拿。

2. 苍穹外卖 JWT 认证的完整链路与核心设计

2.1 一条 token 从签发到校验的完整流程

我习惯把 JWT 认证理解成三个阶段:登录时签发、请求时校验、业务里取人。

第一阶段,客户端调用登录接口,带上用户名和密码。服务端先查数据库,确认账号存在且密码正确,然后把员工 id 或用户 id 放进 JWT 的 claims,用密钥签名生成 token 字符串,最后在登录接口的返回体里把这个 token 交给前端。

第二阶段,前端拿到 token 之后,每次请求在 HTTP Header 里加Authorization: Bearer <token>。后端配置拦截器,拦截指定的接口路径。拦截器先从 Header 里拿 token,没有 token 直接返回未登录,有 token 则解析并验签,解析失败或超时也返回未登录,只有在 token 合法的情况下才放行到 Controller。

第三阶段,Controller 里怎么知道当前登录用户是谁?一种方式是从前端传来的参数里拿,但参数不可信,不能依赖。更靠谱的方式是拦截器在验签通过后,把 claims 里的员工 id 或用户 id 存到 ThreadLocal 里,业务代码直接从 ThreadLocal 取当前用户 id。这样既能保证来源可信,又不需要在每个 Controller 里重复解析 token。

这三个阶段听起来简单,但每个阶段都有细节。比如登录返回的 token 是放 data 里还是单独字段?前端把 token 放在 Header 的什么位置?拦截器放行了哪些路径?ThreadLocal 什么时候清理?这些细节决定了这套方案能不能稳定跑下去。

2.2 JWT 工具类落地:密钥、过期时间、载荷怎么设计

苍穹外卖里一般会封装一个 JwtUtils 或者 JwtUtil 工具类,核心方法就两个:createJWT和parseJWT。一个负责生成 token,一个负责解析并验签。

我自己的实现偏好是这样的:

@Component public class JwtUtils { @Value("${sky.jwt.admin-secret-key}") private String adminSecretKey; @Value("${sky.jwt.admin-ttl}") private long adminTtl; @Value("${sky.jwt.user-secret-key}") private String userSecretKey; @Value("${sky.jwt.user-ttl}") private long userTtl; public String createAdminToken(Long employeeId) { Map<String, Object> claims = new HashMap<>(); claims.put("empId", employeeId); return createToken(adminSecretKey, adminTtl, claims); } public String createUserToken(Long userId) { Map<String, Object> claims = new HashMap<>(); claims.put("userId", userId); return createToken(userSecretKey, userTtl, claims); } private String createToken(String secretKey, long ttl, Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + ttl * 1000)) .signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256) .compact(); } public Claims parseToken(String token, String secretKey) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) .getBody(); } }

这里有一个非常关键的点:密钥必须足够长。HS256 算法是基于 HMAC-SHA256 的对称签名,密钥太短很容易被暴力破解。如果密钥字符串只有六七个字符,虽然能跑通,但安全性几乎为零。所以我建议至少准备 32 个字节以上的字符串。苍穹外卖这个项目里,即便只是学习用途,也应该养成好习惯。

过期时间的设计也要分角色。员工管理端使用频率高,token 过期时间一般不要设太长,我习惯设为 2 小时。用户端可以稍微长一点,比如 7 天,但也要看业务。如果想做“记住我”,可以后面做续签,见第 5 章。

载荷部分只需要放 id 这类必要信息。不要放密码,不要放手机号。JWT 的 Payload 虽然被签名保护,篡改会被发现,但内容是明文编码,任何人拿到都能 Base64 解码看个精光。

2.3 拦截器与 ThreadLocal:用户身份如何跨接口传递

JWT 工具类只是钥匙,真正让整套鉴权跑起来的是拦截器。苍穹外卖里通常会有两个拦截器,一个管管理端,一个管用户端,也可以共用一个拦截器类、通过构造器或配置传入不同密钥。

我比较推荐直接写两个拦截器,虽然代码上有一点冗余,但语义更清楚。管理端拦截器只处理/admin/**,用户端拦截器只处理/user/**,各自的密钥、过期时间、claims 字段名都不一样,混在一起反而容易出错。

拦截器的核心逻辑长这样:

public class JwtTokenAdminInterceptor implements HandlerInterceptor { @Autowired private JwtUtils jwtUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头中获取令牌 String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } // 2. 校验令牌 try { Claims claims = jwtUtils.parseToken(token, jwtUtils.getAdminSecretKey()); Long empId = claims.get("empId", Long.class); BaseContext.setCurrentId(empId); return true; } catch (Exception ex) { response.setStatus(401); response.getWriter().write("NOT_LOGIN"); return false; } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { BaseContext.removeCurrentId(); } }

这里有两个容易忽略的细节。第一,前端传 token 时不一定都带Bearer前缀,需要兼容两种写法,否则自己写 Postman 测试时很容易出现 token 解析失败。第二,登录接口本身不能被这个拦截器拦掉,所以注册拦截器时要把登录路径排除在外。

ThreadLocal 是这套方案里传递用户身份的利器,但也需要小心。ThreadLocal 绑定的数据只对当前线程可见,如果请求结束后不清理,Tomcat 线程池复用时,下一次请求可能拿到上一次残留的用户 id,造成用户信息串号。所以务必在afterCompletion里执行清理。

苍穹外卖项目里一般会提供一个 BaseContext 工具类,内部维护一个ThreadLocal<Long>,提供setCurrentId、getCurrentId、removeCurrentId三个静态方法。业务代码里要用当前登录用户时直接BaseContext.getCurrentId()就好。

2.4 配置文件与密钥管理

密钥、过期时间这些参数不要写死在工具类里,放在配置文件里,既方便修改,也方便不同环境用不同配置。比如管理端和用户端就建议用两套不同的密钥,防止用户端 token 被拿去调管理端接口。

配置示例:

sky: jwt: admin-secret-key: 苍穹外卖管理端JWT密钥请至少32位 admin-ttl: 7200 user-secret-key: 苍穹外卖用户端JWT密钥请至少32位 user-ttl: 604800

如果你是在本地学习,直接写在 application.yml 里没问题。如果是在真实项目里,建议把密钥放到环境变量或配置中心,不要提交到 Git 仓库。密钥一旦泄露,攻击者就能自己签发任意 token,等于整个登录系统被脱了衣服。

另外,生产环境强烈建议给 JWT 配置一个签发时间(iat)和过期时间(exp)。签发时间用来记录 token 产生时刻,过期时间用来控制有效期。很多初学者只设置了过期时间,没有设置签发时间,虽然问题不大,但排查问题时少了一条线索。

3. 实操记录:登录接口签发 token,拦截器统一校验

3.1 从依赖到项目结构:先搭好 JWT 所需的基础

苍穹外卖项目本身是 Spring Boot 工程,要在里面启用 JWT,第一步是引入对应依赖。不同版本的 Spring Boot 对应的 JWT 依赖有差异,比较常见的旧版坐标是:

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

如果你是较新的 Spring Boot 3.x,也可以使用io.jsonwebtoken:jjwt:0.11.5之后的新写法。重点不是版本号,而是要理解jjwt-api是编译期接口,jjwt-impl和jjwt-jackson是运行期实现,三者都必须引入。

依赖加好之后,项目里建议按职责分包:utils放 JWT 工具类,interceptor放拦截器,context放 ThreadLocal 工具类,config放 WebMvc 配置类。分包清晰之后,后面排查问题会省很多时间。

3.2 登录接口改造:密码校验通过后生成 token

登录接口的改造是整个环节里最直观的一部分。原来登录接口的逻辑可能只是查库、比对密码、返回员工信息;改造之后,多了一步:密码匹配成功后,创建 JWT 并放进返回结果。

以管理端登录为例,Service 里的核心逻辑可以写成:

public Employee login(EmployeeLoginDTO employeeLoginDTO) { String username = employeeLoginDTO.getUsername(); String password = employeeLoginDTO.getPassword(); Employee employee = employeeMapper.getByUsername(username); if (employee == null) { throw new AccountNotFoundException("账号不存在"); } // 苍穹外卖里密码一般用 MD5 加密存储 if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(employee.getPassword())) { throw new PasswordErrorException("密码错误"); } if (employee.getStatus() == 0) { throw new AccountLockedException("账号已被锁定"); } return employee; }

Controller 层拿到登录成功的员工对象后,再生成 token:

@PostMapping("/login") public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) { Employee employee = employeeService.login(employeeLoginDTO); String token = jwtUtils.createAdminToken(employee.getId()); EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder() .id(employee.getId()) .userName(employee.getUsername()) .name(employee.getName()) .token(token) .build(); return Result.success(employeeLoginVO); }

这里有一点需要注意:业务逻辑里不要混入太多 JWT 生成代码。登录成功后要不要签发 token,是接口层的事情;要不要校验密码,是服务层的事情。如果 Service 里既校验密码又生成 token,后面如果用户端登录也要生成不同格式的 token,Service 就膨胀了。

3.3 拦截器编写与注册:哪些路径放行,哪些必须拦截

拦截器写完之后,要注册到 WebMvc 配置里才能生效。苍穹外卖一般会有 WebMvcConfiguration 类,实现WebMvcConfigurer,重写addInterceptors方法。

管理端拦截器注册示例:

@Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; @Autowired private JwtTokenUserInterceptor jwtTokenUserInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/employee/login"); registry.addInterceptor(jwtTokenUserInterceptor) .addPathPatterns("/user/**") .excludePathPatterns("/user/user/login"); } }

注册拦截器时最容易出问题的就是路径匹配。/admin/**这种写法在 Spring 的路径规则里是匹配/admin/xxx和/admin/xxx/yyy,但不会匹配/admin本身。如果你希望/admin/employee/login被放行,就必须把完整路径写到excludePathPatterns里。

我见过不少同学把登录接口写在/admin/login或者/admin/employee/login/带斜杠结尾,结果路径和放行配置对不上,请求一来直接被拦截器拦掉。建议写完配置之后,先不要急着调业务,直接用 Postman 打一次登录接口,如果返回 401,先检查是不是路径配置的问题。

3.4 用 Postman 走一遍完整的登录与鉴权流程

本地启动苍穹外卖项目后,我习惯按下面的顺序验证 JWT 链路。

第一步,请求登录接口。以管理端为例,POST/admin/employee/login,Body 里放{"username":"admin","password":"123456"}。如果配置没问题,返回结果里会有一个 token 字段。

第二步,不带 token 访问一个受保护的接口,比如 GET/admin/category/list。预期结果是 401 或者固定提示,比如NOT_LOGIN。这一步主要验证拦截器确实生效了。

第三步,复制登录返回的 token,放到 Postman 的 Authorization 页签,选择 Bearer Token,粘贴进去,再请求同一个接口。这次应该能正常返回业务数据。

很多人到第三步就开始飘了,觉得 JWT 已经搞定。其实你还需要验证第四步:改掉 token 里的任意一个字符,再请求接口。如果系统返回 401,说明验签逻辑正常;如果还能通过,说明签名校验有问题,大概率是解析代码写错了。这个测试很重要,能帮你区分“只是能从 Header 取出 token”和“真正校验了 token 合法性”的区别。

4. 常见问题与排查技巧实录

4.1 token 失效、token 过期,前端还在用旧 token 怎么办

这是最常被问的问题之一。明明登录成功了,过了一段时间再操作,突然返回未登录。原因很简单,token 的exp过期时间到了,服务器验签时发现已经超过有效期,主动拒绝请求。

排查思路分三步:第一步,看前端在请求头里到底有没有把 token 传上来;第二步,看 token 的过期时间是多长,是不是设置得太短;第三步,看服务器时间和前端时间是否有明显差异。如果服务器系统时间被改过,可能导致 token 签发后立刻被认为过期。

如果你需要让用户在较长时间内保持登录,可以考虑两种办法。一种是直接把过期时间调长,简单粗暴,但对安全要求高的场景不推荐。另一种是做一个续签机制,当 token 剩余有效时间低于某个阈值时,通过 refresh_token 或重新登录换取新 token。具体方案见 5.1。

4.2 拦截器放行路径配置错,登录接口被自己拦住了

有一种非常典型的报错:后端启动后,前端调登录接口,直接收到“未登录”。你第一反应是去查登录逻辑,但查了半天发现登录逻辑没错,最后才意识到是拦截器把/admin/employee/login也拦住了。

为什么会出现这个问题?因为很多人在注册拦截器时没有写excludePathPatterns,或者写了但路径和接口实际路径对不上。Spring 的路径匹配规则是精确匹配和通配符匹配,路径末尾多一个少一个斜杠都可能导致匹配失败。

我建议在excludePathPatterns里把登录、Swagger 文档、静态资源等无需鉴权的路径都单独列出来,并且在配置类里加日志输出。启动时看到拦截器注册的拦截路径和放行路径,心里就有底了。

4.3 ThreadLocal 没清理导致用户信息串号

ThreadLocal 用起来很方便,但不能不清理。Tomcat 的工作线程是复用的,线程处理完一个请求后可能被放回线程池,下次再处理另一个请求时,ThreadLocal 里的旧值还在。

具体表现是:用户 A 登录后请求商品列表,用户 B 登录后请求同一个接口,按理说应该各自看到各自的数据,但由于线程复用,B 的请求可能拿到 A 的用户 id,导致数据错乱。这种问题非常隐蔽,不是每次都会复现,而且排查起来很费劲。

解决办法是在拦截器afterCompletion里调用BaseContext.removeCurrentId()。小程序、管理端两个拦截器都要加。这个技巧如果教程里没强调,自己一定要记住。

4.4 JWT 安全加固:常见漏洞与规避手段

JWT 本身不是绝对安全,如果使用不当,会有几个典型漏洞。

第一是弱密钥问题。HS256 是对称签名,密钥就是签名的“密码”,如果密钥太简单,攻击者可以暴力枚举,然后自己伪造 token。密钥建议至少 256 位,并且不要包含常见的单词、日期、键盘连续键。

第二是算法混淆漏洞。JWT 的 Header 里有一个 alg 字段,如果服务端没有限制算法,攻击者可以把 alg 改成none,或利用非对称算法和对称算法的验签方式不同来伪造 token。实现时最好固定签名算法,不要接受none。

第三是载荷泄露问题。不要在 token 里放密码、手机号、身份证号等敏感信息,因为 Payload 是明文编码的。如果有人截获了 token,用 Base64 解码就能看到。

第四是没有主动失效机制。员工离职了、用户被封了,token 仍然有效。实际可以引入 Redis 黑名单,把需要主动失效的 token 存到黑名单里,每次校验时先查黑名单,命中则认为无效。

5. 扩展思考:token 续签、多端登录与本地上传场景

5.1 token 续签怎么做,刷新 token 是否适合苍穹外卖

如果嫌弃固定过期时间不够灵活,可以做一个简单续签。单体项目不一定要上 OAuth 那套 refresh_token 体系,可以使用双 token 或响应头续签。

双 token 方案是登录时同时返回 access_token 和 refresh_token。access_token 过期时间短,比如 2 小时;refresh_token 过期时间长,比如 7 天。前端发现 access_token 过期后,带着 refresh_token 调用统一刷新接口,服务端验证 refresh_token 合法后签发新的 access_token。这种方案需要额外维护 refresh_token 的有效性,通常会把 refresh_token 存在数据库或 Redis,方便撤销。

响应头续签方案更轻量:拦截器在解析 token 时,发现剩余有效期已经低于总有效期的一半,就自动生成一个新 token 放在响应头里,前端在响应头里取到新 token 就替换本地 token。这个方案的优势是不需要额外接口,但需要前端配合,而且每次续签都会刷新过期时间,可能让 token 永不过期。

对于苍穹外卖这类学习项目,我建议先不要过度设计,固定过期时间加到期后重新登录就足够了。续签的优先级排在业务功能之后,等双端登录、权限拦截都稳定了,再考虑也不迟。

5.2 管理端与用户端 token 隔离的实践经验

苍穹外卖里的员工和用户是两套完全独立的账号体系,我在项目中始终坚持一个原则:管理端 token 和用户端 token 使用不同的密钥,加载不同的 claims 字段。

有人觉得用同一个密钥、都放 id 不就够了?不够。如果同一个密钥,用户端 token 被恶意用户拿到,只要他把 token 里的 id 改成员工 id,并且访问/admin/**接口,理论上身份验证能通过。因为服务端只是验签和解析 claims,它不知道这个 token 本应是给用户端用的。虽然员工 id 和用户 id 可能不好蒙,但风险没必要留着。

所以我在JwtUtils里会写两个创建方法,一个createAdminToken,一个createUserToken,两个解析方法也分开。管理端拦截器只认管理端密钥,用户端拦截器只认用户端密钥,两套体系互不干扰。

5.3 本地上传图片接口,token 应该怎么携带

苍穹外卖涉及菜品图片上传,如果做了本地上传图片功能,前端通常用multipart/form-data提交文件。这个场景下,很多人会把 token 放在 Form Data 的参数里,比如和图片一起提交一个token=xxx字段。在单体演示项目里这也能跑通,但不是最佳实践。

更规范的做法是把 token 放在请求头Authorization里,文件本身通过 Form Data 上传。这样拦截器逻辑不用区分是一般接口还是文件上传接口,统一从头取 token。需要注意的是,上传接口和普通 JSON 接口的前端封装可能不一样,要确保文件上传时请求头也会带上 token。

另外,上传接口本身要不要拦截?要看业务需要。如果图片上传后需要定位到具体用户,就要求登录;如果允许匿名上传,就不拦截。这个决定要提前想清楚,不要因为懒直接用/admin/**全拦。

5.4 从单体到微服务,JWT 方案如何平滑演进

苍穹外卖是一个单体项目,JWT 在单体里的实现相对简单:拦截器在同一个进程里做校验,ThreadLocal 在同一进程内传递信息。如果以后项目拆成微服务,JWT 依然可以沿用,但要注意几个变化。

第一,校验点从服务内的拦截器前移到网关层。网关统一拦截请求,完成 token 校验和用户身份解析,再把用户信息通过 Header 转发给下游服务。这样业务服务不需要重复实现 JWT 解析逻辑。

第二,密钥管理变得更重要。多个服务要能验签同一个 token,必须共享同一套密钥或公钥。使用对称密钥时,密钥只能放在配置中心或环境变量里,不能散落各服务仓库。

第三,主动失效更麻烦。单体里用 Redis 黑名单可能还比较好维护,微服务里就需要统一的 Redis 或内存缓存服务。如果业务对安全要求高,也可以在网关层把 token 换成内部临时凭证,下游服务只信任内部凭证,不直接信任原 JWT。

我个人实操中的一个体会是:JWT 真正让人舒服的不是什么玄学技术,而是当你把“生成 token、校验 token、传递用户”这三个环节彻底拆开之后,出问题时能一眼定位到断点。苍穹外卖这个项目把这三件事全都串了起来,你只要亲手敲一遍,再去调几个 401 的 bug,对登录态的理解会比看十篇理论文章都有用。后面如果让我给这个项目加功能,我会优先加 Redis 黑名单和用户端续签,既不破坏现有结构,又能把 JWT 方案真正补成一个可上线的闭环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询