简介
在用户登录功能中,为了增加安全性和验证用户的身份,常常会添加发送验证码的功能。本文将介绍如何在开发中实现登录发送验证码功能。
工具
- 后端:Redis,阿里云
简介(主要介绍限次数功能的实现)
在用户注册、登录等功能中,为了防止恶意发送验证码,常常需要对发送验证码的次数进行限制。本文将介绍如何在开发中实现限制发送验证码次数的功能。
可以使用以下两种方式实现:计数器校验,阿里云返回状态校验
1. 计数器校验
- 借助Redis工具的过期功能,每次向该手机号发送验证码之后,使用计数器增加一次次数,将当前次数同步更新到Redis中,下次在发送验证码时,需要对发送验证码次数进行判断,如果超过限制次数,则拒绝发送验证码。
- (1) 工具类
-
//验证码生成工具类 public class GenerateVerificationCodeUtil { private static final String CHARACTERS = "0123456789"; /*** * 生成随机验证码 * @param length 验证码长度 * @return code */ public static String generateVerificationCode(int length) { StringBuilder code = new StringBuilder(); Random random = new Random(); IntStream.range(0, length).map(i -> random.nextInt(CHARACTERS.length())).forEach(randomIndex -> { char randomChar = CHARACTERS.charAt(randomIndex); code.append(randomChar); }); return code.toString(); } }
(2)阿里云工具类
-
@Component @Slf4j public class AliyunSmsLoader { //配置可以查看阿里云官方教程 private String accessKey; private String secret; private String sysDomain; private String sysVersion; private String regionId; private Boolean isOpen; private DefaultProfile profile = null; /** * 发送短信验证码 * @param phone 电话号码 * @param code 验证码 */zhe public boolean sendCode(String phone, String code) { try { if (isOpen){ if (profile == null){ profile = DefaultProfile.getProfile(regionId, accessKey, secret); } Map<String, String> data = new HashMap<>(); data.put("code", code); CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain(sysDomain); request.setSysVersion(sysVersion); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", regionId); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "Clinflash"); request.putQueryParameter("TemplateCode", SmsTemplateConstants.TemplateEnum.CODE_ZH.getCode()); request.putQueryParameter("TemplateParam", JSONObject.toJSONString(data)); IAcsClient client = new DefaultAcsClient(this.profile); CommonResponse response = client.getCommonResponse(request); Map<String, Object> resMap = (Map<String, Object>) JsonUtils.toObject(response.getData(), Map.class); if (resMap != null && resMap.containsKey("Message") && "OK".equals(String.valueOf(resMap.get("Message")))){ return true; } } } catch (Exception e) { log.error("send sms code error", e); } return false; } }
-
(3)Redis工具类
-
@Component public class RedisUtil { @Resource private RedisTemplate<String, String> redisTemplate; public void set(String key, String value, long timeout) { redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); } public String get(String key) { return redisTemplate.opsForValue().get(key); } public void delete(String key) { redisTemplate.delete(key); } public boolean expire(String key, long timeout, TimeUnit timeUnit) { return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, timeUnit)); } public boolean hasKey(String key) { return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } }
-
(4)计数器
-
public boolean checkSendLimit(String key) { //检查是否超过限制 String key1 = "SEND_LIMIT_" + key;//发送次数在redis中的key标识 int sendCount = 0; int maxSendCount = 5; // 最大发送次数限制 // 获取当前发送次数 Object countObj = redisUtil.get(key1); if (countObj != null) { sendCount = Integer.parseInt(countObj.toString()); } return sendCount >= maxSendCount; // 超过发送次数限制返回 true,否则返回 false } // 增加验证码发送次数 public void incrementSendCount(String key) { // 获取当前发送次数 Object countObj = redisUtil.get(key); int sendCount = (countObj != null) ? Integer.parseInt(countObj.toString()) : 0; // 增加发送次数 sendCount++; redisUtil.set(key, String.valueOf(sendCount), 3600L); // 存储一小时内的发送次数 }
(5)发送验证码
-
//发送验证码功能 public boolean sendCode(String number) { String key = "SEND_LIMIT_" + number; String code = GenerateVerificationCodeUtil.generateVerificationCode(6); aliyunSmsLoader.sendCode(number, code); // 将当前手机号和验证码绑定放到 Redis 中,有效时间五分钟 redisUtil.set(number, code, 300L); incrementSendCount(key); // 增加发送次数 log.info("code: {}", code); return true; }
- 这样每次在发送验证码之前先校验次数,如果超过限制次数,则拒绝发送。
-
boolean isExceedLimit = userService.checkSendLimit(number); //验证码次数校验,一小时内最多5次机会 if (isExceedLimit) { return BaseResult.fail("4005", MessageUtils.get("login.send.limit.exceeded")); }
2. 阿里云返回状态校验
- 在调用阿里云服务的接口时,会有返回的状态。
- 根据状态可以做比较,根据状态值不一样说明发送到达上限,这里就是对返回信息状态进行了校验
-
IAcsClient client = new DefaultAcsClient(this.profile); CommonResponse response = client.getCommonResponse(request); Map<String, Object> resMap = (Map<String, Object>) JsonUtils.toObject(response.getData(), Map.class); if (resMap != null && resMap.containsKey("Message") && "OK".equals(String.valueOf(resMap.get("Message")))){ return true; } }
踩坑
- 阿里云短信触发限制后,会出现手机号收不到验证码但是提示代码显示正常发送情况,以及同一个手机号接收验证码过多也会触发运营商的限制,尽量不要触发级控,保证在可控情景内
纯小白,大佬勿喷也欢迎指点写文技巧