Java框架快速入门: Spring Security+OAuth2之多因子认证缓存服务
2026/9/12 23:37:49 网站建设 项目流程

纲要

  • 多因子认证(MFA)与TOTP核心流程
  • 为何需要缓存用户临时状态
  • 技术选型:Redis+Redisson客户端
  • 核心实现:UserCacheService
    • 生成随机MFA标识并存储用户信息
    • 设置基于TOTP时间步长的过期时间
    • 按MFA ID获取缓存用户
    • 校验TOTP码并清理缓存
  • 嵌入式Redis测试环境
  • 代码结构与可运行示例
  • 启动与验证

背景与问题

在实现基于时间的一次性密码(TOTP)的多因子认证时,用户初次登录后需要输入六位验证码。服务端必须在两次请求之间记住“正在等待MFA验证”的用户信息,且该信息只是临时有效(通常几分钟)。

直接使用数据库会增加无效IO且可能遗留脏数据,引入带有时效的缓存是最佳选择。

本文基于Spring Boot + Spring Security环境,使用Redis作为缓存中间件,Redisson作为访问客户端,封装一个轻量级的UserCacheService,完整展示多因子认证中的缓存逻辑。

依赖与环境

核心依赖在pom.xml中如下所示:

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.5</version></dependency><!-- 嵌入式Redis,仅用于测试 --><dependency><groupId>it.ozimov</groupId><artifactId>embedded-redis</artifactId><version>0.7.3</version><scope>test</scope></dependency>

Redisson负责与Redis交互,嵌入式Redis可以让你在本地测试时无需单独启动Redis服务。

常量定义

为缓存区域命名并统一管理,在常量接口中定义一个字符串常量:

packagecom.example.security.constant;publicinterfaceConstants{StringUSER_MFA_CACHE="userMfaCache";}

这个名称将作为Redis中Map结构的标识。

工具类:随机MFA ID生成

当用户进入MFA验证阶段时,服务端生成一个唯一随机串作为Key,与用户绑定存入Redis。工具类如下:

packagecom.example.security.util;importorg.springframework.stereotype.Component;importjava.security.SecureRandom;@ComponentpublicclassRandomUtil{privatestaticfinalStringCHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";privatefinalSecureRandomrandom=newSecureRandom();publicStringgenerateRandomString(intlength){StringBuildersb=newStringBuilder(length);for(inti=0;i<length;i++){sb.append(CHARS.charAt(random.nextInt(CHARS.length())));}returnsb.toString();}}

这里生成12位字母数字混合串,可根据安全需求调整长度。

核心服务:UserCacheService

该Service负责:

  1. 缓存待验证的用户(cacheUser),返回MFA ID。
  2. 根据MFA ID取出用户(getUser)。
  3. 校验TOTP码,并清除缓存(verifyTotp),返回验证成功的用户。

完整实现如下:

packagecom.example.security.service;importcom.example.security.constant.Constants;importcom.example.security.model.User;importcom.example.security.util.RandomUtil;importlombok.RequiredArgsConstructor;importorg.redisson.api.RMapCache;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Service;importjava.util.Optional;importjava.util.concurrent.TimeUnit;@Service@RequiredArgsConstructorpublicclassUserCacheService{privatefinalRedissonClientredissonClient;privatefinalRandomUtilrandomUtil;/** * 缓存用户,返回MFA ID * @param user 待验证用户 * @return 生成的MFA ID */publicStringcacheUser(Useruser){RMapCache<String,User>cache=redissonClient.getMapCache(Constants.USER_MFA_CACHE);StringmfaId=randomUtil.generateRandomString(12);// 如果缓存中不存在该ID,则放入if(!cache.containsKey(mfaId)){intttlInSeconds=TotpUtil.DEFAULT_TIME_STEP_SECONDS;// 例如300秒cache.put(mfaId,user,ttlInSeconds,TimeUnit.SECONDS);}returnmfaId;}/** * 根据MFA ID获取缓存的用户 * @param mfaId 前端带回的MFA ID * @return Optional包装的用户 */publicOptional<User>getUser(StringmfaId){RMapCache<String,User>cache=redissonClient.getMapCache(Constants.USER_MFA_CACHE);if(cache.containsKey(mfaId)){returnOptional.ofNullable(cache.get(mfaId));}returnOptional.empty();}/** * 验证TOTP码,成功则删除缓存并返回用户 * @param mfaId MFA ID * @param code 用户提交的验证码 * @return 验证成功返回用户,否则返回Optional.empty() */publicOptional<User>verifyTotp(StringmfaId,Stringcode){RMapCache<String,User>cache=redissonClient.getMapCache(Constants.USER_MFA_CACHE);// 先检查缓存是否存在if(!cache.containsKey(mfaId)){returnOptional.empty();}UsercachedUser=cache.get(mfaId);if(cachedUser==null){returnOptional.empty();}try{byte[]secretKey=Base64.getDecoder().decode(cachedUser.getMfaSecret());if(TotpUtil.verifyTotp(secretKey,code)){// 验证成功,移除缓存cache.remove(mfaId);returnOptional.of(cachedUser);}}catch(Exceptione){// 密钥无效等异常,视为验证失败}returnOptional.empty();}}

要点说明:

  • RMapCache提供了带TTL的Map结构,put(key, value, ttl, timeUnit)会在TTL到期后自动清除。
  • TTL取自由TotpUtil定义的DEFAULT_TIME_STEP_SECONDS(通常300秒即5分钟),与TOTP窗口保持一致。
  • verifyTotp中解码用户的MFA密钥(Base64存储),调用TotpUtil.verifyTotp验证动态码,成功后移除缓存,保证一次性使用。

TOTP工具参考

为完整性,给出TotpUtil中的关键常量与方法签名:

packagecom.example.security.util;publicclassTotpUtil{publicstaticfinalintDEFAULT_TIME_STEP_SECONDS=300;// 5分钟publicstaticbooleanverifyTotp(byte[]secret,Stringcode){// 使用TOTP算法校验,此处省略具体实现,可使用Google Auth库}}

用户实体示例

packagecom.example.security.model;importlombok.Data;@DatapublicclassUser{privateLongid;privateStringusername;privateStringpassword;privatebooleanusingMfa;privateStringmfaSecret;// Base64编码的TOTP密钥}

集成到认证流程

下面是一个简化的Controller展示如何调用:

packagecom.example.security.controller;importcom.example.security.model.User;importcom.example.security.service.UserCacheService;importlombok.RequiredArgsConstructor;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.*;importjava.util.Map;importjava.util.Optional;@RestController@RequestMapping("/auth")@RequiredArgsConstructorpublicclassAuthController{privatefinalUserCacheServiceuserCacheService;/** * 第一因子认证通过后,生成MFA ID返回给前端 */@PostMapping("/mfa/init")publicResponseEntity<?>initMfa(@RequestBodyUserauthenticatedUser){StringmfaId=userCacheService.cacheUser(authenticatedUser);returnResponseEntity.ok(Map.of("mfaId",mfaId));}/** * 前端提交MFA ID和验证码完成验证 */@PostMapping("/mfa/verify")publicResponseEntity<?>verifyMfa(@RequestParamStringmfaId,@RequestParamStringcode){Optional<User>verifiedUser=userCacheService.verifyTotp(mfaId,code);if(verifiedUser.isPresent()){// 签发JWT或创建SecurityContextreturnResponseEntity.ok(Map.of("token","generated_jwt_token"));}returnResponseEntity.status(401).body("MFA verification failed");}}

启动与验证

1. 启动Redis

在开发环境可使用Docker快速启动:

dockerrun-d--nameredis-p6379:6379 redis:7-alpine

2. 配置application.yml

spring:redis:host:localhostport:6379# Redisson 自动配置,无需额外配置

3. 验证接口

/auth/mfa/init发送第一因子认证通过的用户JSON,得到mfaId,随后调用/auth/mfa/verify?mfaId=xxx&code=123456完成MFA验证。

项目代码结构

src/main/java/com/example/security/ ├── constant/ │ └── Constants.java ├── controller/ │ └── AuthController.java ├── model/ │ └── User.java ├── service/ │ └── UserCacheService.java └── util/ ├── RandomUtil.java └── TotpUtil.java

总结

本文介绍了在 Spring Security + OAuth2 环境下实现 TOTP 多因子认证时,通过 Redis 缓存临时用户状态的完整方案。

核心思路是:用户通过第一因子认证后,服务端生成随机 MFA ID,将用户对象以该 ID 为键存入带 TTL 的 Redis Map 缓存(TTL 与 TOTP 时间步长一致)。验证时前端回传 MFA ID 与动态码,服务端取出用户、校验 TOTP 并立即删除缓存,确保一次有效。

采用 Redisson 的 RMapCache 简化过期控制,结合嵌入式 Redis 完成测试,最终将缓存服务融入认证 Controller,形成可运行的轻量级多因子认证缓存层。

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

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

立即咨询