通过redis防止短信恶意调用
1.场景
登录或注册接口中,使用短信验证码场景时,遇到恶意调用短信接口,一毫秒多次调用,导致短信资源大幅度消耗。
2.排查
经排查发现,短信发送接口无限制,仅通过redis保留验证码发送信息。一个时间戳产生数次甚至十几次调用,多个线程同时访问redis后,取值相同,均通过验证前往调用短信接口。
3.解决方案
使用redis分布式锁,解决该问题。
相关代码如下:
3.1 redis锁实现
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final Long NX = 1L; // 超时时间
public boolean tryLock(String key, String value) {
// 超时时间单位,这里发现,较新版本的RedisTemplate中,setIfAbsent方法时间单位为TimeUnit,并非大多帖子中表述的Long类型
TimeUnit timeUnit = TimeUnit.MINUTES;
// 这里设置redis锁超时时间为1分钟
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, NX, timeUnit);
return result != null && result;
}
// unlock方法未测试,请测试后再使用
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else " +
"return 0 " +
"end";
redisTemplate.execute((RedisCallback<Boolean>) connection -> {
Object nativeConnection = connection.getNativeConnection();
if (nativeConnection instanceof Jedis) {
return ((Jedis) nativeConnection).eval(script, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
} else if (nativeConnection instanceof RedisClusterConnection) {
return ((RedisClusterConnection) nativeConnection).eval(script.getBytes(), ReturnType.INTEGER, 1, key.getBytes(), value.getBytes()).equals(1L);
}
return false;
});
}
}
3.2 方法调用
public boolean getVerfityCode(String mobile) {
String smsCode = ShareCodeUtils.smsCode();
// redis锁参数
String lockKey = "sms_lock_" + mobile;
String lockValue = UUID.randomUUID().toString();
boolean send = false;
try {
if (redisLock.tryLock(lockKey, lockValue)) {
// 获取锁成功,触发短信验证码功能(具体逻辑,此处因原登录逻辑需要redis支持,故保留)
String s = redisUtils.get(RedisKeys.VERFITY_CODE + mobile);
if (s != null) {
// 判断验证码是否过期
throw new ServiceException("验证码未过期,请勿重复获取");
}
// 发送验证码
send = smsBaoUtil.sendSms(mobile, smsCode);
if (send) {
// reids 中不存在 此电话对应验证码证明已经过了 verCodeTm 秒,则可以重新生成
redisUtils.set(RedisKeys.VERFITY_CODE + mobile, smsCode, verCodeTm);
}
}
} catch (Exception e) {
send = false;
}
// 很多帖子这里会增加finally解锁逻辑,这里为了保证1分钟内不会再触发短信恶意调用,取消解锁逻辑,由redis超时销毁后,自动解锁。
return send;
}