SpringBoot14-集成Redis
一、SpringBoot集成Redis
1-1、先理解:什么是 Redis?
Redis 就是一个超快的数据库,但它专门存 数据在内存里(不是硬盘)。
你可以把它想象成:
一个超级快的“笔记本”,用来暂时记一些数据。
比如:登录状态、验证码、排行榜、购物车、点赞数……
为什么用 Redis?
因为它:
速度非常快(比 MySQL 快几十~上百倍)
可以自动过期(适合存验证码、token)
可以解决并发问题(比如抢票、库存扣减)
1-2、第一步:安装 Redis
(1)如果你用 Windows:
下载 Redis for Windows(无需安装)
https://github.com/tporadowski/redis/releases
打开 redis-server.exe 就启动了。
(2)如果是 Mac:
brew install redis
brew services start redis
启动成功后,Redis 会在 6379 端口上运行。
(3)验证是否安装了redis:

1-3、第二步:在 Spring Boot 项目中加依赖
在 pom.xml 加:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
就这样,Redis 支持就进项目了。
1-4、第三步:配置连接 Redis
在 application.yml 里写:
spring:data:redis:host: 127.0.0.1 # 本机port: 6379 # 默认端口
如果你 Redis 设置了密码,这里再加
password: xxx
1-5、第四步:写一个最简单的 Redis 代码(存 + 取)
1、写一个 Service 类:
@Service
public class RedisTestService {@Autowiredprivate StringRedisTemplate stringRedisTemplate; // 专门操作字符串// 存数据public void saveData() {stringRedisTemplate.opsForValue().set("name", "Tom");}// 取数据public String getData() {return stringRedisTemplate.opsForValue().get("name");}
}
Maven中添加了redis的依赖之后,StringRedisTemplate就能直接注入使用了!
2、写个 Controller 调用它
@RestController
public class TestController {@Autowiredprivate RedisTestService redisTestService;@GetMapping("/save")public String save() {redisTestService.saveData();return "保存成功!";}@GetMapping("/get")public String get() {return redisTestService.getData();}
}
3、运行测试
浏览器访问:
① 保存数据:
http://localhost:8080/save
② 获取数据:
http://localhost:8080/get
你将看到返回:
Tom
你已经成功用 Spring Boot 操作 Redis 了!
4、或者直接写一个redis的测试类

1-6、小结

二、令牌主动失效
目标:
登录成功 → 把发给浏览器的令牌同时存进 Redis;
每次请求在拦截器里比对“浏览器带来的令牌 == Redis里为该用户保存的令牌”;
用户改密成功 → 主动删除 Redis 里的旧令牌,使其立刻失效。
2-1、Redis 存储设计(双向映射,便于比对和失效)
auth:user:{userId} -> {token}(用户当前有效 token,便于改密时一把踢下线)auth:token:{token} -> {userId}(用 token 找到用户,拦截器校验时用)
两个 key 都设置相同 TTL(比如 2 小时);再次登录会覆盖旧 token。
2-2、登录:生成并落库(含顶替旧令牌)
@Service
@RequiredArgsConstructor
public class AuthService {private final StringRedisTemplate redis;// 令牌有效期private static final long TOKEN_TTL_MINUTES = 120;public String login(Long userId, String rawPassword) {// 1. 这里省略账号密码校验(查库比对哈希)// if (!passOK) throw new BizException(...);// 2. 生成新 tokenString token = UUID.randomUUID().toString();// 3. 顶替旧 token(如果有就删)String oldToken = redis.opsForValue().get("auth:user:" + userId);if (oldToken != null) {redis.delete("auth:token:" + oldToken);}// 4. 双向写入 + 过期时间redis.opsForValue().set("auth:user:" + userId, token, TOKEN_TTL_MINUTES, TimeUnit.MINUTES);redis.opsForValue().set("auth:token:" + token, String.valueOf(userId), TOKEN_TTL_MINUTES, TimeUnit.MINUTES);// 5. 返回给前端return token;}// 退出登录:前端丢弃 + 服务端删除public void logout(String token) {if (token == null) return;String userId = redis.opsForValue().get("auth:token:" + token);if (userId != null) {redis.delete("auth:token:" + token);redis.delete("auth:user:" + userId);}}// 改密后:让旧令牌立即失效(主动删除)public void invalidateUserTokenOnPasswordChange(Long userId) {String oldToken = redis.opsForValue().get("auth:user:" + userId);if (oldToken != null) {redis.delete("auth:user:" + userId);redis.delete("auth:token:" + oldToken);}}
}
示例 Controller(返回 token 给前端;退出;改密后失效):
@RestController
@RequiredArgsConstructor
public class AuthController {private final AuthService authService;@PostMapping("/login")public String login(@RequestParam Long userId, @RequestParam String password) {return authService.login(userId, password);}@PostMapping("/logout")public void logout(@RequestHeader("Authorization") String token) {authService.logout(token);}// 改密成功后调用:旧 token 立刻失效@PostMapping("/password/change")public String changePwd(@RequestParam Long userId,@RequestParam String oldPwd,@RequestParam String newPwd) {// 1) 校验旧密码并更新为新密码(略)// 2) 主动失效旧令牌authService.invalidateUserTokenOnPasswordChange(userId);// 3) 可选:直接让用户重新登录,或这里返回新 tokenreturn "OK";}
}
2-3、拦截器校验:比对“请求令牌 == Redis 存的令牌”
@Component
@RequiredArgsConstructor
public class LoginInterceptor implements HandlerInterceptor {private final StringRedisTemplate redis;@Overridepublic boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {String token = req.getHeader("Authorization");if (token == null || token.isEmpty()) {resp.setStatus(401);return false;}// 1) 通过 token 找 userId(无则说明 token 无效或过期)String userId = redis.opsForValue().get("auth:token:" + token);if (userId == null) {resp.setStatus(401);return false;}// 2) 再用 userId 取当前有效 token,必须“完全相等”才算有效String currentToken = redis.opsForValue().get("auth:user:" + userId);if (!token.equals(currentToken)) {// 出现这种情况:用户在别处重新登录/改了密码/已被踢下线resp.setStatus(401);return false;}// ✅ 校验通过return true;}
}
注册拦截器:
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {private final LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); // 登录接口放行}
}
2-4、前端怎么带令牌
登录后保存服务端返回的
token之后每个请求都在请求头带上:
Authorization: <token>
(名字可自定义,统一即可)
2-5、可选增强
自动续期(活跃会话不掉线)
在拦截器校验通过后,给两个 key 续命(重置 TTL):
// 校验通过后
redis.expire("auth:token:" + token, 120, TimeUnit.MINUTES);
redis.expire("auth:user:" + userId, 120, TimeUnit.MINUTES);
单设备登录
上面的“顶替旧令牌”已经实现了单设备登录——新登录会让旧 token 失效。多端共存
把auth:user:{userId}改成 Set,允许保存多个 token(PC/手机各一个);
改密时删除该用户所有 token。
