短信验证手机号

在日常使用各种app或者登陆网站的时候,基本都会看到短信验证码这个功能,实现短信验证码的方式有很多,我这里给出基于SpringBoot+Redis的短信验证码实现方式

把一切都简化,短信验证码的实现无法就是下面几点:

1、后端随机生成四位到八位的随机数字作为短信的验证码,在redis中保存一定时间(一般是5分钟)。

2、调用第三方短信接口将短信验证码发给用户。

3、用户输入短信验证码提交后,在后端与之前生成的短信验证码作比较,如果相同说明验证成功,否则验证失败。

这样 一个很简单的短信验证手机号的接口就写好了 但是 这样简单的短信接口有着被恶意调用 也就是被刷的风险

常见的刷短信验证的行为有以下两种

1.以攻击手机号为目的刷短信验证码

这类攻击目标主要是攻击者借助web网站短信接口对目标手机号进行短信轰炸。攻击者会先收集互联网上多个未经防护的网站短信接口,设定要攻击的手机号码通过模拟用户,循环向后台发送短信验证码请求,达到攻击手机号的目的。

2.以恶意刷取目标网站短信费用为目的的攻击

这类攻击主要目的是刷掉目标网站的短信费用,在第一种基础上攻击者会不停变换各种接口参数如手机号、IP(采用高匿代理)等去请求后台发送短信验证码,进行恶意刷短信,后台根本无力辨别用户真伪。攻击目标明确,难以防护,因其变换不同IP、手机号,一些简单措施基本失效

针对这些攻击者 我们需要做出一些应对的措施

首先是增加图形验证码 当用户进行“获取短信验证码”操作前,弹出图形验证码 要求用户输入验证码后 服务器端才发送动态短信到用户手机上 但如果攻击者忽略验证码验证错误的情况 大量的执行请求会给服务器带来额外的负担 我建议在服务器端限制单个IP在单位时间内的请求次数 一旦用户请求次数超出设定的阈值 则停止对该IP一段时间的请求 若情节特别验严重的 也可以将该IP加入黑名单 最后就是限制重复发送动态短信的间隔时长 即当单个用户发送一次动态短信之后 服务器限制只有在一定时长之后 一般是60s 才能进行第二次动态短信请求

package com.huawei.roma.auth.verify.utils;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

/**
 * redis hash 漏斗限流
 *
 * @author liaojiamin
 * @Date:Created in 16:57 2020/5/29
 */
public class FunnelRateLimiter {

    private Jedis client;

    public FunnelRateLimiter(Jedis client) {
        this.client = client;
    }

    /**
     * 请求是否成功
     */
    public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate, int quota) {
        String key = this.key(userId, actionKey);
        long nowTs = System.currentTimeMillis();
        Map<String, String> funnelMap = client.hgetAll(key);
        if (funnelMap == null || funnelMap.isEmpty()) {
            return initFunnel(key, nowTs, capacity, quota);
        }
        long intervalTs = nowTs - Long.parseLong(funnelMap.get("leakingTs"));
        int intervalCapacity = (int) (intervalTs * leakingRate);
        // 时间过长, int可能溢出
        if (intervalCapacity < 0) {
            intervalCapacity = 0;
            initFunnel(key, nowTs, capacity, quota);
        }
        // 腾出空间必须 >= 1
        if (intervalCapacity < 1) {
            intervalCapacity = 0;
        }
        int leftCapacity = Integer.parseInt(funnelMap.get("leftCapacity")) + intervalCapacity;
        if (leftCapacity > capacity) {
            leftCapacity = capacity;
        }
        return initFunnel(key, nowTs, leftCapacity, quota);
    }

    /**
     * 存入redis,初始funnel
     */
    private boolean initFunnel(String key, long nowTs, int capacity, int quota) {
        Map<String, String> funnelMap = new HashMap<>();
        funnelMap.put("leftCapacity", String.valueOf((capacity > quota) ? (capacity - quota) : 0));
        funnelMap.put("leakingTs", String.valueOf(nowTs));
        client.hmset(key, funnelMap);
        return capacity >= quota;
    }

    /**
     * 限流key
     */
    private String key(String userId, String actionKey) {
        return String.format("limit:%s:%s", userId, actionKey);
    }

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        FunnelRateLimiter limiter = new FunnelRateLimiter(jedis);
        for (int i = 1; i <= 30; i++) {
            boolean success = limiter.isActionAllowed("liziba", "view", 40, 20f / 86400000f, 1);
            System.out.println("第" + i + "请求" + (success ? "成功" : "失败"));
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值