详解Spring Boot中的JWT令牌管理策略

从Session到JWT:Web认证技术演进与Spring Boot实战迁移指南 身份认证是Web应用安全架构的核心基础,其本质是在客户端与服务器之间建立可信的身份声明机制。传统基于Session的认证采用中心化状态管理,服务器存储会话数据并通过Cookie传递Session ID,虽保障了安全,但在微服务与分布式架构下面临扩展性瓶颈和状态同步难题。Token-Based Authentication应运而生,通过密码学签名将认证信息编码成自包含令牌,实现了去中心化的无状态验证,使服务器集群获得天然的水平扩展能力。JWT作为该领域的开放标准,以其标准化的三段式结构和自包含声明,广泛应用于 阅读详情

为了安全起见,使用无状态JWT令牌时可以使用短时限TTL(1分钟)策略,然后这些令牌会在其生存时间内及时刷新。如果服务器不知道用户何时注销,那么可以继续刷新已注销用户的令牌。本文将提供针对这个问题的一种解决方案,使之在保持水平扩展性的同时确保安全性能不受影响。

架构设计

从图中展示的体系架构可见,每个微服务都有自己的数据库。被撤销的令牌和用户都需要单一(身份)信息源(Single Source of Truth,简称“SSOT”)。数据库需要具有高可用性,包括多主机、热备份及数据库的其他功能。其中,撤销的令牌数据库只需要两个表:一个用于用户注销时来缓存撤销的令牌,此令牌由负责缓存撤销令牌表中内容的微服务每90秒调用一次;另一个用于用户登录。每次注销后,微服务都会在定义的行生存时间内更新撤销的令牌表,并且登录是有速度限制的。因此,上述体系结构减少了吊销令牌数据库的负载,使其能够扩展到更大的部署。被撤销令牌的单一(身份)信息源要求是必要的,因为每个用户请求都可以在任何微服务上处理,并且需要在那里检查撤销的令牌。需要通过用户表来支持微服务登录用户。这样一来就可以将安全检查的负载分散到各个微服务。该架构中,JWT令牌是在微服务的内存中进行检查的,而且只增加了一点CPU损耗,不要求使用IO负载。

实现代码分析

为了验证上述结论,我开发了一个MovieManager项目来实现撤销令牌的处理。首先,我们来看登录部分实现代码。

登录操作

为了支持已撤销的令牌,登录时首先要检查用户当前已撤销令牌的数量,并降低登录速度,以限制用户可以生成的已撤销令牌的数量。这一部分功能是在UserDetailsMgmt服务中完成的,其中关键部分代码如下:

private UserDto loginHelp(Optional<User> entityOpt, String passwd) {
   UserDto user = new UserDto();
   Optional<Role> myRole = entityOpt.stream()
      .flatMap(myUser -> Arrays.stream(Role.values())
   .filter(role1 -> Role.USERS.equals(role1))
           .filter(role1 -> 
              role1.name().equals(myUser.getRoles()))).findAny();
   if (myRole.isPresent() && entityOpt.get().isEnabled()
    && this.passwordEncoder.matches(passwd, entityOpt.get().getPassword())) {
   Callable<String> callableTask = () -> this.jwtTokenService
           .createToken(entityOpt.get()
              .getUsername(), Arrays.asList(myRole.get()), Optional.empty());
        try {
      String jwtToken = executorService
              .schedule(callableTask, 3, TimeUnit.SECONDS).get();
      user = this.jwtTokenService
             .userNameLogouts(entityOpt.get().getUsername()) > 2 ? 
                user : this.userMapper.convert(entityOpt.get(), 
                   jwtToken, 0L);
   } catch (InterruptedException | ExecutionException e) {
      LOG.error("Login failed.", e);
   }
   }
   return user;
}

在上述代码中,首先筛选出用户实体User的可选角色,然后检查用户实体User是否存在以及其是否具有Users角色,是否已启用,以及密码是否匹配。

然后,创建一个Callable来为用户创建JWT令牌。该令牌中包含有Username和UUID,用于在注销时标识每个令牌。在不同的线程池上以3秒的延迟执行Callable,以限制用户在更新撤销的令牌缓存之间可以进行的注销次数。

接下来,检查是否为用户缓存了超过2个已撤销的令牌。如果为真,则拒绝登录。

总之,上述两项检查确保可以限制用户可生成的已撤销令牌数量以及登录时的负载。

为了实现水平可扩展性,必须将数据表移动到RevokedToken数据库中。

注销操作

注销操作是在UserDetailsMgmt服务中实现,代码如下:

public Boolean logout(String bearerStr) {
   if (!this.jwtTokenService.validateToken(
      this.jwtTokenService.resolveToken(bearerStr).orElse(""))) {
   throw new AuthenticationException("Invalid token.");
   }
   String username = this.jwtTokenService.getUsername(
      this.jwtTokenService
        .resolveToken(bearerStr).orElseThrow(() -> 
           new AuthenticationException("Invalid bearer string.")));
   String uuid = this.jwtTokenService
      .getUuid(this.jwtTokenService.resolveToken(bearerStr)
    .orElseThrow(() -> 
             new AuthenticationException("Invalid bearer string.")));
   this.userRepository.findByUsername(username).orElseThrow(() -> 
      new ResourceNotFoundException("Username not found: " + username));
   long revokedTokensForUuid = this.revokedTokenRepository.findAll().stream()
   .filter(myRevokedToken -> myRevokedToken.getUuid().equals(uuid)
      && myRevokedToken.getName().equalsIgnoreCase(username)).count();
   if (revokedTokensForUuid == 0) {
      this.revokedTokenRepository.save(new RevokedToken(username, uuid,  
         LocalDateTime.now()));
   } else {
      LOG.warn("Duplicate logout for user {}", username);
   }
   return Boolean.TRUE;
}

上述代码中,首先检查JWT令牌是否有效。接下来,从JWT令牌中读取用户名和UUID。然后,使用令牌中的用户名检查用户表Users中的用户数据。接下来,使用相同的UUID和UserID调用revokedTokens以进行记录检查。如果找到对应的记录,将记录一条关于重复注销尝试的警告。如果是第一次注销JWT令牌,则会在吊销的令牌表中创建一个新的包含当前用户名、UUID及当前时间等数据的RevokedToken实体。

为了实现水平扩展性,也必须将数据表移动到RevokedToken数据库。

撤销令牌缓存更新问题

撤销的令牌缓存任务是使用CronJobs组件进行更新的:

@Scheduled(fixedRate = 90000)
public void updateLoggedOutUsers() {
   LOG.info("Update logged out users.");
   this.userService.updateLoggedOutUsers();
}

每隔90秒从表中读取一次数据。更新操作在UserDetailsMgmt服务中处理:

public void updateLoggedOutUsers() {
   final List<RevokedToken> revokedTokens =
      new ArrayList<RevokedToken>(this.revokedTokenRepository.findAll());
   this.jwtTokenService.updateLoggedOutUsers(
      revokedTokens.stream().filter(myRevokedToken -> 
         myRevokedToken.getLastLogout() == null || 
         !myRevokedToken.getLastLogout()
         .isBefore(LocalDateTime.now()
         .minusSeconds(LOGOUT_TIMEOUT))).toList());
   this.revokedTokenRepository.deleteAll(
     revokedTokens.stream().filter(myRevokedToken -> 
        myRevokedToken.getLastLogout() != null && myRevokedToken
         .getLastLogout().isBefore(LocalDateTime.now()
            .minusSeconds(LOGOUT_TIMEOUT))).toList());          
}

上述代码中,首先从列表中读取所有被撤销的令牌。然后,删除早于LOGOUT_TIMEOUT(185秒)的记录;其他的则缓存在JwtTokenService中。

JwtTokenService负责管理撤销令牌的缓存:

public record UserNameUuid(String userName, String uuid) {}
private final List<UserNameUuid> loggedOutUsers = 
   new CopyOnWriteArrayList<>();

public void updateLoggedOutUsers(List<RevokedToken> revokedTokens) {
   this.loggedOutUsers.clear();
   this.loggedOutUsers.addAll(revokedTokens.stream()
     .map(myRevokedToken -> new UserNameUuid(myRevokedToken.getName(), 
   myRevokedToken.getUuid())).toList());
}

上述代码中,UserNameUuid记录数据中包含具有标识令牌的值。loggedOutUsers列表中提供了已注销用户或者已撤销令牌的用户名UUID。其中,CopyOnWriteArrayList是线程安全的。

接下来,UpdateLogeDoutUsers方法获取已撤销令牌的当前列表,清除并更新loggedOutUsers列表——该列表用于令牌验证。

JWT令牌验证

JWT令牌中包含了一个用户名和哈希值,这些都需要进行验证。现在,JWT令牌也会根据loggedOutUsers列表进行检查,以检查注销情况。这部分任务是在下面的JWT令牌过滤器部分完成的:

@Override
public void doFilter(ServletRequest req, ServletResponse res, 
   FilterChain filterChain) throws IOException, ServletException {
   String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
   if (token != null && jwtTokenProvider.validateToken(token)) {
      Authentication auth = token != null ?  
         jwtTokenProvider.getAuthentication(token) : null;
      SecurityContextHolder.getContext().setAuthentication(auth);
   }
   filterChain.doFilter(req, res);
}

上述代码中,在处理请求前先调用JwtTokenFilter。首先,从HTTP头部数据中读出令牌。然后,检查令牌是否已找到且有效(validateToken(…)。最后,在SecurityContextHolder中创建并设置身份认证。

实现令牌验证的Java代码如下所示:

public boolean validateToken(String token) {
   try {
      Jws<Claims> claimsJws = Jwts.parserBuilder()
        .setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token);
      String subject = Optional.ofNullable(
        claimsJws.getBody().getSubject()).orElseThrow(() -> 
           new AuthenticationException("Invalid JWT token"));
      String uuid = Optional.ofNullable(claimsJws.getBody()
         .get(JwtUtils.UUID, String.class)).orElseThrow(() -> 
             new AuthenticationException("Invalid JWT token"));
      return this.loggedOutUsers.stream().noneMatch(myUserName -> 
         subject.equalsIgnoreCase(myUserName.userName) && 
         uuid.equals(myUserName.uuid));
   } catch (JwtException | IllegalArgumentException e) {
      throw new AuthenticationException("Expired or invalid JWT token",e);
   }
}

在上述代码中,首先解析令牌,检查签名密钥,读取声明:否则,抛出异常。然后,读取主题(userName)和UUID。接下来,根据loggedOutUsers检查令牌。如果所有检查都正常,则令牌有效,请求得到处理。

小结

在上述方案中,令牌的生存时间为60秒。注销令牌写入撤销令牌表后,缓存每90秒更新一次。被撤销的令牌在表中保留185秒。这意味着,每个令牌在所有缓存中都需要刷新。然后,刷新将失败,令牌从而不再有效。登录的速度限制确保用户可以在撤销的令牌表中创建的条目数量有限。所有这些都限制了RevokedToken数据库上的负载,从而增加它可以处理的微服务的数量。

因此,有了这样的体系结构便可降低令牌丢失的风险。同时,基于JWT令牌身份验证的分布式安全检查可让大部分可拓展性优势得以保持。

作为补充,对于微服务中的同步时钟,可以使用NTP技术实现。文章​ ​《Ubuntu中的同步技术》​ ​提供了有关实现此技术的操作指南。另外,文章​ ​《基于Spring+Angular的JWT自刷新解决方案》​ ​中也展示了一个基于Angular前端如何处理令牌的例子。

Spring Boot实战:集成JWT实现无状态Token认证与权限控制 本文详细介绍了如何在Spring Boot项目中集成JWT实现无状态Token认证与权限控制。通过解析JWT核心组成、集成Spring Security、实现动态权限控制等实战方案,帮助开发者构建安全高效的认证系统。文章还涵盖了Token自动续期、黑名单管理等高级特性,并提供了性能优化建议和生产环境最佳实践。 阅读详情

相关推荐

一文详解jwt token以及sprig boot如何整合实现 jwt token操作

一文详解jwt token以及sprig boot如何整合实现 jwt token操作。

念兮为美 2015

SpringBoot集成JWT实现token验证,token注销

JWT官网: [https://jwt.io/][https_jwt.io] JWT(Java版)的github地址:[https://github.com/jwtk/jjwt][https_github.com_jwtk_jjwt] 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).**定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。**因为数字签名的存在,这些信息是可信的

zzhongcy的专栏 3454

LLC混合滞回控制 vs 电压模/电流模:UCC25640x仿真对比与选型指南

在开关电源设计中,LLC谐振变换器的环路补偿是核心难点。传统电压模控制(VMC)和电流模控制(CMC)在LLC拓扑上存在环路增益漂移、动态响应慢等局限。混合滞回控制通过滞回比较器直接调制开关频率,并融合输出电压与谐振腔电流信息,实现了自适应环路增益和快速瞬态响应。该技术显著提升轻载效率与动态性能,适用于高密度适配器、快充及车载充电机等场景。本文基于UCC25640x系列,通过仿真对比三种控制策略,为工程选型与调试提供实践参考。

weixin_33925615的博客 333

Spring Security + JWT 实现认证和授权

数据库表 Spring Security JWT JwtToken @Data public class JwtToken { private String token; private String username; private Long expireTime; } JwtTokenProvider @Slf4j @Component public class JwtTokenProvider { public JwtToken cre..

修理男爵的博客 2462

手把手教会你用yolov8从环境配置到检测任务实战

做本文章前,我浏览过很多网上资料,我发现他们总是写的含糊不清,而且实际操作起来就会有各种各样的问题,要么就是根本不适合一些小白,所以我在这里写一篇完全针对小白的yolo从入门到实战。看完本文章后大家都可以实现下图效果,数字识别是一种比较简单的检测任务,其他识别任务学完本例也能够一通百通,大家边做边学,更好的体会yolo的神奇之处。

KevenDuan的博客 2871

手把手教你用vivado2018.3为复旦微JFMQL15T打IP补丁(含环境变量配置技巧)

本文详细介绍了在Vivado 2018.3环境中为复旦微JFMQL15T开发板打IP补丁的全流程,包括环境变量配置、IP缓存管理等关键步骤。通过实战验证的解决方案,帮助工程师快速搭建开发环境,解决国产芯片开发中的常见问题,提升开发效率。

weixin_30374009的博客 723

Spring Boot + Security + JWT 实现Token验证+多Provider——登录系统

首先呢就是需求: 1、账号、密码进行第一次登录,获得token,之后的每次请求都在请求头里加上这个token就不用带账号、密码或是session了。 2、用户有两种类型,具体表现在数据库中存用户信息时是分开两张表进行存储的。 为什么会分开存两张表呢,这个设计的时候是先设计的表结构,有分开的必要所以就分开存了,也没有想过之后Security 这块需要进行一些修改,但是分开存就分开存吧,Secu...

weixin_30252155的博客 791

HyperLPR3车牌识别-Android-SDK光速部署与使用

基于深度学习高性能中文车牌识别 High Performance Chinese License Plate Recognition Framework.快速部署AndroidSDK

tunm 6117

JWT令牌管理完全指南:SpringBoot-Security-JWT中的Token生成与刷新机制

在当今的Web应用开发中,**JWT令牌管理**已经成为保护API安全的关键技术。SpringBoot-Security-JWT项目提供了一个完整的解决方案,帮助开发者轻松实现基于JWT的认证与授权系统。本文将深入探讨JWT令牌的生成与刷新机制,为您提供一份实用的**Spring Security JWT**实现指南。 ## 🎯 什么是JWT令牌? JSON Web Token(JWT)是一

gitblog_01068的博客 458

项目管理知识体系(PMBOK)中的质量规划详解

你是否遇到过这些场景?软件上线后用户投诉“功能漏洞多”,团队连夜返工;装修完的房子墙面开裂,施工方和业主互相推诿;新研发的手机电池续航不达标,被迫延迟发布。这些问题的根源,往往是项目启动时没有做好质量规划。本文聚焦PMBOK中“质量规划”(Plan Quality Management)过程,覆盖其核心概念、操作步骤、工具技术与实战应用,适用于IT、建筑、制造等各类项目场景。

项目管理的博客 1117

clock min pulse width检查

高电平脉宽 = clock fall edge到达FF/CK的时间 - clock rise edge到达FF/CK的时间;低电平脉宽 = 下一T clock rise edge到达FF/CK的时间 - clock fall edge到达FF/CK的时间。而required pulse width是根据FF/CK处的transition,根据查找表计算得到的。为什么clock transition越大,会导致required pulse width越大呢?5、 降低clock上的串扰。

芯冰乐 577

在 ChatGPT Retrieval Plugin 中使用 Azure Cosmos DB MongoDB vCore 搭建向量检索后端

本文是 ChatGPT Retrieval Plugin 中 Azure Cosmos DB 向量数据库提供方的完整配置指南。围绕 `docs/providers/azurecosmosdb/setup.md` 的核心内容,结合仓库中的源码实现([azurecosmosdb_datastore.py](https://link.gitcode.com/i/40ac93abad2cb546a9a27

gitblog_00188的博客 776

2020计算机专业保研夏令营面经:中科院计算所网数机试题目

计算所网络数据科学与技术重点实验室的第一阶段是机试。机试不限程序语言,要求提交源代码。

乔卿的博客 2957

Spring Boot JWT安全最佳实践:防范XSS攻击与Token管理策略

在现代Web应用开发中,Spring Boot结合JWT(JSON Web Token)已成为构建安全认证系统的主流选择。本文将深入探讨Spring Boot JWT应用中防范XSS攻击的关键技术和Token管理的最佳实践,帮助开发者构建更安全、更可靠的认证机制。 ## JWT认证的安全挑战 JWT作为一种轻量级的身份验证机制,虽然带来了无状态、跨域等优势,但也存在独特的安全风险。根据项目[R

gitblog_00665的博客 981

模型预测控制(MPC)解析(三):多输入多输出系统

本文为阅读笔记,仅供学习交流使用!!! 在之前的文章中,为了简单起见,以单输入单输出系统为例介绍了MPC控制的实现方法。因为使用的是状态空间方程作为系统模型,这种方法可以轻松的扩展到多输入多输出系统中。 ...

行者无疆的博客 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Spring Boot集成JWT实现API安全认证:从原理到实战

在微服务与RESTful API架构中,API安全是保障服务间通信和数据交互的基石。其核心原理在于建立一套可靠的认证与授权机制,确保请求的合法性与数据完整性。JWT(JSON Web Token)作为一种流行的无状态令牌技术,通过其自包含的三段式结构(Header、Payload、Signature)和数字签名,为分布式系统提供了高效的身份验证方案。该技术的工程价值在于实现了服务端的无状态化,简化了水平扩展,并易于跨域使用。典型的应用场景包括用户登录认证、微服务间安全调用以及前后端分离架构中的身份管理。本文

weixin_33722405的博客 360

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

探索Spring Boot之旅:JWT+Redis打造高效双Token认证体系

探索Spring Boot之旅:JWT+Redis打造高效双Token认证体系 去发现同类优质开源项目:https://gitcode.com/ 在这个数字化时代,确保应用的安全性变得至关重要。今天,我们为您呈现一个融合前沿技术的开源项目——Spring Boot整合JWT与Redis实现双Token校验的实战案例。本项目不仅展示了如何巧妙结合技术力量,还为企业级应用提供了一套高效、安全的认证解决...

gitblog_09768的博客 540

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

great-big-example-application 安全机制详解:JWT 认证与权限控制实现

在当今的Web应用开发中,**安全机制**是每个应用都必须重视的核心部分。Great Big Example Application作为一个全栈示例应用,采用了一套完善的**JWT认证**和**权限控制**系统来保护应用安全。本文将深入解析这个项目的**安全架构**,帮助开发者理解如何在实际项目中实现可靠的用户认证和授权机制。🚀 ## 🔐 JWT认证机制:现代Web应用的安全基石 Grea

gitblog_00328的博客 713

Weave Router辅助推理三兄弟:标题生成、分类器与probe的turn-type硬固定

Weave Router 是一个面向 Agent 系统的**模型路由器**:把 Claude Code、Codex 等客户端发来的每个 prompt 在 50ms 内路由到最合适的模型,只需改一个 endpoint 就能砍掉 40-70% 的推理成本。但 Agent 客户端除了"正经对话"外,还会偷偷发起三类**辅助推理**小请求——**标题生成(TitleGen)、分类器(Classifier)

gitblog_01132的博客 716

Spring Boot、Spring Security、Oauth2实现的权限控制和认证服务

新一代基于Spring Boot、Spring Security、Oauth2等实现的权限控制和认证服务、支持第三方oauth授权和获取资源信息功能等、详情请点击下面的项目地址查看,欢迎大家使用体验,觉得不错的给个star,谢谢

https://gitee.com/micai-code 3949

StatsD 指标类型完全指南:Counter、Timer、Gauge、Set 协议详解与源码剖析

StatsD 是一个运行在 Node.js 平台上的网络守护进程,通过 UDP/TCP 接收计数器(counters)、计时器(timers)、仪表盘(gauges)和集合(sets)等统计指标,并在每个 flush 周期内聚合并发送给一个或多个可插拔后端服务(如 Graphite)。本文以 [docs/metric_types.md](https://link.gitcode.com/i/236

gitblog_00596的博客 199

实战指南:构建高安全性的Spring Boot 3微服务认证中心

本文详细介绍了如何基于Spring Boot 3和Spring Security 6构建高安全性的微服务认证中心,整合JWT和Redis实现分布式环境下的安全认证方案。通过核心安全配置、JWT与Redis深度整合以及认证流程完整实现,帮助开发者掌握构建高性能、高可用认证系统的关键技术。

weixin_29267307的博客 266

JWT身份验证:如何实现注销?

可能的解决方案: 将JWT存储在数据库中。您可以检查哪些令牌有效以及哪些令牌已被撤销,但这在我看来完全违背了使用JWT的目的。 从客户端删除令牌。这将阻止客户端进行经过身份验证的请求,但如果令牌仍然有效且其他人可以访问它,则仍可以使用该令牌。这引出了我的下一点。 令牌生命周期短。让令牌快速到期。根据应用,可能是几分钟或半小时。当客户端删除其令牌时,会有一个很短的时间窗口仍然可以使用它。从客户...

Lycode 1万+
上一篇: Java多线程专题之线程与进程概述
下一篇: Spring Cloud OpenFeign 的五个优化小技巧
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值