Appearance
2026-08-30 · rbac-system 登录链路里 Redis 的三种用法:防爆破计数、会话登记、权限缓存。
Redis 实战(以 rbac-system 登录链路为例)
1. 解决什么问题
登录链路里 Redis 干三件事,对应我们在 debug 时看到的三处代码:
| 位置 | Redis 干的活 | 数据结构 | key |
|---|---|---|---|
AuthService.isLoginLocked() | 防爆破:记连续输错次数 | String 计数 | login:fail:{用户名} |
OnlineSessionServiceImpl.createSession() | 会话登记(在线用户/能登出) | Hash | login:token:{jti} |
PermCacheServiceImpl.cachePerms() | 权限缓存(减少查库) | Set | auth:perms:{userId} |
2. 环境:启动本机 Redis
Windows 用 scoop 装的 Redis,带密码配置文件启动:
bash
cd C:/Users/zgqin/scoop/apps/redis/current && redis-server redis.conf(桌面有「启动Redis.vbs」可无窗启动。本机密码 rbac@redis789,记在 redis.conf 的 requirepass)
验证:
bash
redis-cli -a rbac@redis789 ping # 返回 PONG 即正常(没有 -a 密码会报 NOAUTH)3. 依赖与配置
pom.xml:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>application.yaml(注意是 spring.data.redis.*,spring.redis.* 是旧前缀不生效):
yaml
spring:
data:
redis:
host: localhost
port: 6379
password: rbac@redis789
timeout: 3stimeout 3s 很重要:Redis 挂了时接口快速失败返回提示,不挂死。
4. 用法一:防爆破计数(登录第一步)
AuthService 里的三段代码,用 StringRedisTemplate 做原子计数:
java
// 登录前判断是否锁定
private boolean isLoginLocked(String username) {
String count = redisTemplate.opsForValue().get(LOGIN_FAIL_KEY + username);
return count != null && Integer.parseInt(count) >= LOGIN_MAX_FAILS;
}
// 失败一次:INCR 自增,首次设置 15 分钟 TTL
private void recordLoginFail(String username) {
Long fails = redisTemplate.opsForValue().increment(LOGIN_FAIL_KEY + username);
if (fails != null && fails == 1L) {
redisTemplate.expire(LOGIN_FAIL_KEY + username, Duration.ofMinutes(15));
}
}
// 登录成功:删 key,连续失败链条终结
private void clearLoginFail(String username) {
redisTemplate.delete(LOGIN_FAIL_KEY + username);
}要点:
INCR是原子的,并发安全;达到 5 次即锁定,TTL 到期自动解锁- Redis 异常时降级:
try-catch (DataAccessException)包住,Redis 挂了不阻断登录,只丢防爆破
5. 用法二:会话登记(登录成功后)
OnlineSessionServiceImpl,登录成功把会话写进 Redis hash,TTL 24h:
java
@RequiredArgsConstructor
@Service
public class OnlineSessionServiceImpl implements OnlineSessionService {
private final StringRedisTemplate redisTemplate;
@Override
public void createSession(String jti, Long userId, String username, ...) {
String key = KEY_PREFIX + jti; // login:token:{jti}
Map<String, String> m = new HashMap<>();
m.put("userId", String.valueOf(userId));
m.put("username", username);
// ... 昵称/组织/IP/登录时间等
redisTemplate.opsForHash().putAll(key, m);
redisTemplate.expire(key, Duration.ofSeconds(24 * 3600));
}
}这个 key 是三个功能的地基:
- 在线用户列表:SCAN 前缀收集
- 登出/强退:
redisTemplate.delete(key)删掉 → 该会话下次请求 401 - 权限变更即时生效:删该用户会话 → 强制重登拿新权限
6. 用法三:权限缓存(登录后预热)
PermCacheServiceImpl,用 Set 存权限点编码,读时只做 contains 判断:
java
public void cachePerms(Long userId, Collection<String> pointCodes) {
String key = KEY_PREFIX + userId; // auth:perms:{userId}
redisTemplate.delete(key);
if (!CollectionUtils.isEmpty(pointCodes)) {
redisTemplate.opsForSet().add(key, pointCodes.toArray(new String[0]));
}
redisTemplate.expire(key, Duration.ofHours(12)); // 12h 兜底
}
public Set<String> getPerms(Long userId) {
if (!Boolean.TRUE.equals(redisTemplate.hasKey(key))) return null;
return redisTemplate.opsForSet().members(key);
}要点:
- 用 Set 不用 String(JSON):判断"有没有某权限"直接
SISMEMBER,不用反序列化 - TTL 只是兜底,正常走"变更即删":改权限后
evict(userId)删 key,下次请求查库回填,即"权限变更即时生效" - Redis 异常返回 null → 拦截器走 DB 查询兜底,鉴权不中断
7. 复刻时记住的三条规则
- key 设计:
业务:资源:{id}前缀式,如login:token:{jti}—— 同类 key 好 SCAN、好排查 - Redis 必须能挂:所有读写包
try-catch (DataAccessException),Redis 挂了降级(放行/查库),不能拖垮主流程 - SCAN 不用 KEYS:
keys *会阻塞单线程 Redis,用 SCAN 游标分批扫
8. 常见坑
| 现象 | 解法 |
|---|---|
| 启动报 Redis 连接失败 | 先 redis-cli -a 密码 ping 确认 Redis 活着;检查 yaml 前缀是不是 spring.data.redis.* |
| 配置了密码但连不上 | redis-server 必须带 redis.conf 启动(不带=无密码默认配置) |
| 接口挂起 60s 才返回 | Lettuce 默认超时太长,yaml 加 timeout: 3s |
| 登出了 token 还能用 | 会话 key 没删(redisTemplate.delete(login:token:{jti})),或没做 Redis 会话校验 |