马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
条件
用户手机号发送验证码, 需要考虑以下几点
- 每天发送的频率(同一个手机号每天发送N条,比如限制每天发送15条)
- 发送短信的时间隔断 (比如60秒以后才能继续发送下一条)
- 验证码的逾期时间(比如: 5分钟)
- 验证码最大验证次数(触发验证接口, 输入的验证码在多次失败验证下,能触发频频验证接口提示验证失败)
- 验证码验证后,颁发一个验证Token的时效性(这个根据业务情况进行设置Token,是一次性还是偶然效性)
验证码表计划
- create table t_sms_code
- (
- id bigint not null comment '主键'
- primary key,
- country_code int not null comment '国家代码',
- mobile varchar(50) not null comment '手机号',
- type int null comment '短信类型, 1 用户模块',
- code varchar(6) null comment '验证码',
- token varchar(400) null comment '验证码token',
- day_send_times varchar(400) null comment '当日发送时间列表',
- send_count int null comment '发送次数',
- send_time bigint null comment '发送时间',
- verify_count int null comment '验证次数',
- verify_time bigint null comment '验证时间'
- )
- comment '短信验证码';
- create index country_code_mobile
- on t_sms_code (country_code, mobile);
复制代码 要点参数设置
- public class SmsProperties {
- /**
- * 短信发送次数限制
- */
- private Integer maxSmsCountInDay = 10;
- private Long smsSendInterval = 60 * 1000L;
- /**
- * 验证码过期时间
- */
- private Long smsCodeExpireTime = 5 * 60 * 1000L;
- /**
- * 验证码验证次数限制
- */
- private Integer smsMaxVerifyCount = 3;
- /**
- * 验证码token失效时间, 1 days
- */
- public Long smsTokenInvalidTime = 24 * 60 * 60 * 1000L;
- /**
- * 更加业务需要是否使用jwt-Token, 比如可以使用随机字符串作为Token
- * RandomStringUtils.random(64, 0, 0, true, true, null, new SecureRandom()).toUpperCase()
- */
- public String secretKey = "user@jwt@miyao";
- }
复制代码 配合Mybatis, 编写相关API
生成验证码
- public Boolean generatePinAndSendSms(Integer countryCode, String phoneNumber) throws SmsException{
- SmsCode sms = getSmsCodeByPhoneNumber(countryCode, phoneNumber);
- List<Long> daySmsRecordTimes = Lists.newArrayList();
- if (Objects.nonNull(sms)) {
- daySmsRecordTimes = getDaySmsRecordTimes(sms);
- if (!daySmsRecordTimes.isEmpty() && daySmsRecordTimes.size() >= smsProperties.getMaxSmsCountInDay()) {
- log.warn("PIN code to phone number '{}' was not sent because the maximum number of sends allowed by the SMS operator within 24 hours has been exceeded.",
- phoneNumber);
- throw new SmsException(SmsException.ExceptionType.TOO_MANY);
- }
- if (sms.getType() != null && sms.getType() == 1 && sms.getSendCount() != null && sms.getSendTime() != null) {
- checkSendingInterval(sms.getSendCount(), sms.getSendTime(), phoneNumber);
- }
- }
- // Generate and send the notification
- String pin = generatePin();
- String[] parameters = new String[]{pin};
- Boolean sendResult = sendSmsNotification(countryCode, phoneNumber, 1, parameters, true);
- if (!sendResult) {
- return false;
- }
- // Save PIN to DB
- long timeMillis = System.currentTimeMillis();
- daySmsRecordTimes.add(timeMillis);
- Integer currentSendCount = Optional.ofNullable(sms).filter(s -> s.getSendCount() != null).map(s -> s.getSendCount() + 1).orElse(1);
- SmsCode smsCode = new SmsCode()
- .setCountryCode(countryCode)
- .setMobile(phoneNumber)
- .setCode(pin)
- .setType(1)
- .setToken(null)
- .setDaySendTimes(JSONObject.toJSONString(daySmsRecordTimes))
- .setSendTime(timeMillis)
- .setSendCount(currentSendCount)
- .setVerifyTime(null)
- .setVerifyCount(null);
- if (sms == null) {
- smsCode.setId(IdWorker.getId());
- this.insert(smsCode);
- } else {
- smsCode.setId(sms.getId());
- this.update(smsCode);
- }
- return true;
- }
- public SmsCode getSmsCodeByPhoneNumber(Integer countryCode, String phoneNumber) {
- return this.selectLimitOne(Query.of(new SmsCode().setMobile(phoneNumber)
- .setType(1).setCountryCode(countryCode)).wrapper());
- }
- private List<Long> getDaySmsRecordTimes(SmsCode smsCode) {
- if (smsCode.getDaySendTimes() == null) {
- return Lists.newArrayList();
- }
- List<Long> daySendTimes = JSONObject.parseArray(smsCode.getDaySendTimes(), Long.class);
- // day start time
- long dayStartTime = DateUtil.beginOfDay(new Date()).getTime();
- List<Long> sendTimes = daySendTimes.stream().filter(s -> s > dayStartTime).sorted().collect(Collectors.toList());
- return sendTimes;
- }
- private void checkSendingInterval(Integer sendCount, Long sendTime, String phoneNumber) throws SmsException {
- long currentTime = System.currentTimeMillis();
- Long sendInterval = smsProperties.getSmsSendInterval() != null ? smsProperties.getSmsSendInterval() : 60 * 1000L;
- // The SMS pin code can be sent only once within 50 seconds
- if (currentTime < sendInterval) {
- log.warn("PIN code to phone number '{}' was not sent because the requests are too frequent. Please try again after {}.",
- phoneNumber, sendTime + sendInterval);
- throw new SmsException(SmsException.ExceptionType.TOO_MANY);
- }
- // Wait five minutes after sending three messages
- if (sendCount % 3 == 0) {
- if (currentTime < sendTime + 5 * 60 * 1000L) {
- log.warn("PIN code to phone number '{}' was not sent because of {} sends have blocked sending until {}",
- phoneNumber, sendCount, sendTime + 5 * 60 * 1000L);
- throw new SmsException(SmsException.ExceptionType.TOO_MANY);
- }
- }
- }
- private String generatePin() {
- int min = 100000;
- int max = 1000000;
- int randomNum = rand.nextInt((max - min)) + min;
- return String.valueOf(randomNum);
- }
复制代码 校验验证码
- public SmsVerifyResult checkPhoneAndPin(Integer countryCode, String phoneNumber, String pin, String ipAddress) throws SmsException {
- if (StringUtils.isBlank(pin)) {
- log.warn("PIN verification for phone number '{}' was rejected because PIN is null or whitespace", phoneNumber);
- throw new SmsException(SmsException.ExceptionType.NOT_FOUND);
- }
- SmsCode sms = getSmsCodeByPhoneNumber(countryCode, phoneNumber);
- if (sms == null) {
- log.info("PIN verification for phone number '{}' was rejected because verification was not found", phoneNumber);
- throw new SmsException(SmsException.ExceptionType.NOT_FOUND);
- }
- if (StringUtils.isBlank(sms.getCode())) {
- log.warn("PIN verification for phone number '{}' was rejected because pin was not found", phoneNumber);
- throw new SmsException(SmsException.ExceptionType.NOT_FOUND);
- }
- if (StringUtils.isNotBlank(sms.getToken())) {
- log.warn("PIN verification for phone number '{}' was rejected because it was already verified", phoneNumber);
- throw new SmsException(SmsException.ExceptionType.CODE_VALIDATED);
- }
- long timeMillis = System.currentTimeMillis();
- if (sms.getSendTime() != null && timeMillis > (sms.getSendTime() + smsProperties.getSmsCodeExpireTime())) {
- log.info("PIN verification for phone number '{}' was rejected because it occurred too long after code was sent", phoneNumber);
- throw new SmsException(SmsException.ExceptionType.CODE_EXPIRED);
- }
- Integer currentVerifyCount = sms.getVerifyCount() != null ? sms.getVerifyCount() + 1 : 1;
- SmsCode smsCode = new SmsCode()
- .setId(sms.getId())
- .setCountryCode(sms.getCountryCode())
- .setMobile(sms.getMobile())
- .setCode(sms.getCode())
- .setType(sms.getType())
- .setToken(sms.getToken())
- .setDaySendTimes(sms.getDaySendTimes())
- .setSendTime(sms.getSendTime())
- .setSendCount(sms.getSendCount())
- .setVerifyCount(currentVerifyCount)
- .setVerifyTime(timeMillis);
- if (!pin.equals(sms.getCode())) {
- updateCurrentVerifyCount(currentVerifyCount, smsCode);
- if(currentVerifyCount >= smsProperties.getSmsMaxVerifyCount()){
- log.warn("PIN verification for phone number '{}' rejected and {} failed attempts have set pin to invalid",
- phoneNumber, currentVerifyCount);
- throw new SmsException(SmsException.ExceptionType.CODE_VALIDATE_TOO_MANY);
- }
- throw new SmsException(SmsException.ExceptionType.CODE_INVALID);
- }
- SmsVerifyToken smsVerifyToken = new SmsVerifyToken()
- .setCountryCode(countryCode)
- .setPhoneNumber(phoneNumber)
- .setIp(ipAddress);
- // 此处根据自己的业务情况,使用一次性字符串Token还是什么有意义的token?
- String token = JwtUtils.createJwt(UUID.randomUUID().toString(), JsonUtils.toJSONString(smsVerifyToken), smsProperties.getSecretKey(), smsProperties.getSmsTokenInvalidTime());
- smsCode.setToken(token)
- .setCode(null)
- .setSendCount(null)
- .setSendTime(null);
- this.update(smsCode);
- SmsVerifyResult smsVerifyResult = new SmsVerifyResult();
- smsVerifyResult.setVerifyToken(smsCode.getToken());
- // 如果有其他信息,根据自己业务设置, 或者直接返回,token这个字符串
- return smsVerifyResult;
- }
复制代码 在进行业务流程时,校验验证码Token信息
- public boolean checkPhoneAndToken(Integer countryCode, String phoneNumber, String token) {
- if (StringUtils.isBlank(token)) {
- log.warn("Token verification for phone number '{}' was rejected because of an invalid token", phoneNumber);
- return false;
- }
- SmsCode sms = getSmsCodeByPhoneNumber(countryCode, phoneNumber);
- if (sms == null) {
- log.warn("Token verification for phone number '{}' was rejected because verification was not found", phoneNumber);
- return false;
- }
- if (!token.equals(sms.getToken())) {
- log.warn("Token verification for phone number '{}' was rejected because the token did not match with the saved token", phoneNumber);
- return false;
- }
- try {
- Claims claims = JwtUtils.parseJwt(token, smsProperties.getSecretKey());
- String subject = claims.getSubject();
- SmsVerifyToken smsVerifyToken = JSONObject.parseObject(subject, SmsVerifyToken.class);
- if (smsVerifyToken.getPhoneNumber() == null || !phoneNumber.equals(smsVerifyToken.getPhoneNumber())
- || !countryCode.equals(smsVerifyToken.getCountryCode())) {
- log.warn("Token verification for phone number '{}' was rejected because the token did not match with the saved token", phoneNumber);
- return false;
- }
- Date expiration = claims.getExpiration();
- if (expiration.before(new Date())) {
- log.warn("Token expired");
- return false;
- }
- } catch (Exception e) {
- return false;
- }
- // long timeMillis = System.currentTimeMillis();
- // if (sms.getVerifyTime() == null || timeMillis > sms.getVerifyTime() + smsProperties.getSmsTokenInvalidTime()) {
- // log.warn("Token verification for phone number '{}' was rejected because the token was verified too long ago", phoneNumber);
- // return false;
- // }
- return true;
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |