用Redis的zset防御Session Flood
zset (Sorted Set)是set的升级版本, 在set的基础上增加了一个顺序(或者权重)值属性, 属性在添加修改元素时候可以指定. 每次指定后zset会自动重新按新的值调整顺序. 可以理解为有两列字段的数据表, 一列存value, 一列存顺序编号.
在Redis中, 就会保存一系列的列表, 每个列表是某个IP最近的新建session的记录. 服务端在每次创建session时, 根据ip将此时的(session id, timestamp)记录加入一个zset, 用removeRangeByScore清理时间跨度之外的数据, 检查数量是否超出限制, 最后将整个zset的过期时间更新一下.
对于新建session数量超出的IP, 可以停止新建session, 或将其放入全局黑名单.
@Override public int insert(SessionDTO dto) { // ANTI FLD RedisZset keyIp = redisFactory.getZset(ID + ":ip:" + dto.getIp()); keyIp.removeRangeByScore("-inf", String.valueOf(System.currentTimeMillis() - RATE_TIME_FRAME)); if (keyIp.size() >= RATE_THRESHOLD) { logger.error("ANTI FLD: {}", dto.getIp()); return 0; } keyIp.add(dto.getId(), System.currentTimeMillis()); keyIp.exipre(RATE_TIME_FRAME); // ANTI FLD END redisFactory.pipeline( (Pipeline pipeline) -> { pipeline.hset((ID + ":db").getBytes(), dto.getId().getBytes(), SerializeUtil.serialize(dto)); pipeline.zadd(ID + ":" + ORDERBY[0], dto.getUpdatedAt(), dto.getId()); pipeline.zadd(ID + ":" + ORDERBY[1], dto.getStartedAt(), dto.getId()); long expiredAt = dto.getUpdatedAt() + ((dto.getAutologin() == 0) ? ((dto.getUserId().equals(UserDTO.ANONYMOUS_UID)) ? 0 : TTL_LOGIN) : TTL_AUTOLOGIN); pipeline.zadd(ID + ":" + ORDERBY[2], expiredAt, dto.getId()); pipeline.zadd(ID + ":" + ORDERBY[3], SerializeUtil.score(dto.getUserId()), dto.getId()); }); return 1; }