• 请不要在回答技术问题时复制粘贴 AI 生成的内容
kisshere
V2EX  ›  程序员

一个接口,浏览器每请求 100 次,弹出一次验证码,要求服务器端不记录浏览器对应 ip 的请求次数,该怎么实现?

  •  
  •   kisshere · Dec 27, 2019 · 10699 views
    This topic created in 2515 days ago, the information mentioned may be changed or developed.

    如果通过服务器端记录客户端对应 ip 的请求次数,数据库会撑爆的,如何从验证客户端浏览器提供的数据下手,让这个接口,浏览器每请求 100 次,就弹出一次验证码?

    $_COOKIE['requestCount']=$_COOKIE['requestCount']+1;
    if($_COOKIE['requestCount']>100){show_captcha();}//这种逻辑就算了哈,小学生都能修改 cookie 破解
    
    Supplement 1  ·  Dec 27, 2019
    最终我还是采用简单粗暴的方法:rand(1,100)>98 && show_captcha();
    什么 cookie 加密和 Redis 和 memcache 存 ip 都弱爆了
    Supplement 2  ·  Dec 27, 2019
    评论区提醒,random 的方式也不对,我还是老老实实用 Redis 记录 ip 递增访问次数吧
    89 replies  •  2019-12-30 14:43:50 +08:00
    nianyu
    nianyu
    Dec 27, 2019
    1
    你 又想安全 又要完全从客户端入手.办不到啊
    fancy111
    fancy111
    Dec 27, 2019
    2
    你都说了验证客户端浏览器,那就 cookie 计数加密一下咯。
    或者 localStorage 什么的,反正存客户端嘛。
    但是有什么用?客户端随时可以清空的,你的计数不可能完全保存在客户端。
    kisshere
    kisshere
    OP
    Dec 27, 2019❤️1
    3
    @fancy111 第一次请求就显示验证码,这个也可以接受,即用户清空 cookie,就显示验证码
    sagaxu
    sagaxu
    Dec 27, 2019 via Android❤️5
    4
    random
    lhx2008
    lhx2008
    Dec 27, 2019 via Android
    5
    数据库会存爆?每天清就行了,你想存爆挺难的。
    qinxg
    qinxg
    Dec 27, 2019
    6
    放内存里啊
    phpdever
    phpdever
    Dec 27, 2019
    7
    你的目的是防止客户端对接口滥用,解决思路应该是从后端角度处理

    怕数据库撑爆,可以用 Redis,简单点的,用计数器法就可以实现限流
    cominghome
    cominghome
    Dec 27, 2019
    8
    如果通过服务器端记录客户端对应 ip 的请求次数,数据库会撑爆的

    1. 这个数据量,其实没你想的大
    2. 为什么要放数据库? memcached 不好用还是 redis 不香?
    kisshere
    kisshere
    OP
    Dec 27, 2019
    9
    @cominghome
    @phpdever
    @lhx2008 用户用代理池来爬接口,数据库很可能冒烟
    annielong
    annielong
    Dec 27, 2019
    10
    服务端返回数据的时候加一个 100 递减的参数,客户端请求数据时候必须带这个参数,
    virusdefender
    virusdefender
    Dec 27, 2019
    11
    cookie 加密一下,虽然也不能根本性的解决这个问题
    zhenhuaYang
    zhenhuaYang
    Dec 27, 2019
    12
    redis 的 incr 命令不香吗?
    lower
    lower
    Dec 27, 2019
    13
    感觉你的 100 次限制,可能设置成在某个时间段里的限制,会不会好处理一些,比如是每天或者每个小时。
    服务端随便记录,时间一到,缓存自动失效或者定时清数据库。
    kisshere
    kisshere
    OP
    Dec 27, 2019
    14
    @virusdefender cookie 可逆式加密也不能解决问题,用户每次携带同样的加密 cookie 给服务器端,服务器端每次都判断请求次数<100 而放行通过
    kisshere
    kisshere
    OP
    Dec 27, 2019
    15
    @lower 我目前也是这么想的,只能通过失效时间上解决问题,而不能在请求次数上做文章,所以看看有没有 V 站大佬能通过客户端请求次数想个好办法
    lhx2008
    lhx2008
    Dec 27, 2019 via Android
    16
    @kisshere 我说的数据库是内存数据库,冒不了烟,TPS 随便都是几万的,你如果不把 IP ban 掉,网站还早挂了
    zzzmh
    zzzmh
    Dec 27, 2019
    17
    要么前端解决要么后端解决
    前端解决肯定不是绝对 100%安全,只能说相对来说压缩加密的 js 不容易调试和被改。推荐你了解一下 Fingerprintjs2
    后端用 nginx + lua 貌似可以,但我没写过类似的,只在论坛见过
    wwcxjun
    wwcxjun
    Dec 27, 2019
    18
    Redis + 1,最简单的是使用 IP 作为 key,值就是请求次数, 判断值到一百就弹验证码,验证完了清空值就好了. Redis 不用考虑会撑爆了吧... 还是怕的话 可以加个失效时间
    chendy
    chendy
    Dec 27, 2019
    19
    这种需求一定是服务端做,别人真想搞你的话客户端就是给人看接口的,不想搞你客户端做一堆防护也没用
    另外如果有必要的话就直接在业务上做限制,比如未登录用户只能看一点数据,登陆了可以看多一些,充值了随便看之类的
    alphatoad
    alphatoad
    Dec 27, 2019 via iPhone
    20
    这个问题可以近似为弹出验证码的几率是 1%
    然后就不用说了吧(狗头
    catror
    catror
    Dec 27, 2019 via Android
    21
    IP 请求次数放内存里就完事了,如果要多台服务器全局限制的话就用 redis。
    wml
    wml
    Dec 27, 2019
    22
    @alphatoad 这样可能会导致用户体验不好 且想攻击重新请求一下就好了
    jiaming1992
    jiaming1992
    Dec 27, 2019
    23
    可以给用户发个通知,让用户记录,每请求一次在本本上画一笔,等到 100 个时让用户通知你。
    zlhsvc
    zlhsvc
    Dec 27, 2019
    24
    内存里就完事了,然后加个过期清理,你不可能永久记录这个次数吧
    forgottencoast
    forgottencoast
    Dec 27, 2019
    25
    @kisshere #3 说的就可以啊,客户端 Cookie 加密,如果没有携带参数就出验证码,这样清理 Cookie 这招就没用了。
    Cookie 带 IP 地址,一换 IP 地址就弹验证码。
    kisshere
    kisshere
    OP
    Dec 27, 2019
    26
    @forgottencoast 比如用户请求了 3 次,3 经过加密成为一个字符串 asdf,用户 F12 抓到这个 asdf 后,每次都携带这个字符串 asdf 给提交给服务器,服务器每次解密后得出是 3,然后放行通过。。。。。。
    snoopyxdy1
    snoopyxdy1
    Dec 27, 2019
    27
    用 cookie 签名做就可以拉,第一次的话就无脑弹验证码
    noahsophie
    noahsophie
    Dec 27, 2019
    28
    浏览器每请求 100 次,弹出一次验证码。这个就是防范普通用户的,所以前端怎么方便怎么来,其次服务端可以在网关层做处理,比如在 nginx 限制 IP 访问频率
    darknoll
    darknoll
    Dec 27, 2019
    29
    nginx 不行么
    lihongjie0209
    lihongjie0209
    Dec 27, 2019
    30
    Ip + counter 放内存不就行了?
    数据库会爆炸??? 全球的 IP 都会访问你们的接口?
    passerbytiny
    passerbytiny
    Dec 27, 2019❤️2
    31
    首先,客户端 IP 不是浏览器能决定的,那是中间网络决定的,你后面提到用代理池怕接口,那应该是想到这一点了。

    然后,每请求 100 次弹出一次验证码这个逻辑不对,应该是:若短时间内连续请求 N 次,则接下来一段时间内必弹验证码,然后重置。一个 IP 的请求次数,并不是静态永久数据,而是动态瞬时数据,它的记录在首次请求的时候创建,在重置,或者超过监控时间的时候删除。(如果有心思,还可以考虑更花哨的重置和监控时机。)

    最后,记录客户端对应 ip 的请求次数,一个 IP 就一条记录,如果要达到数据库爆仓的效果,那得短时间内几千万个新 IP 访问服务器,这时候你首要考虑的就不是爆仓,而是 DDOS 了。

    所以,安心的记录吧,当然还是建议 Redis 记录,除了性能考虑外,你还可以不用做重置处理(由 redis 超时机制自动重置)
    Sunyanzi
    Sunyanzi
    Dec 27, 2019❤️2
    32
    这需求能做 ... 不需要记录用户 IP ... 需要用户传四个元素 ... counter / nonce / timestamp / signature ...

    如果不严谨要求不能挨重放的话 nonce 也可以去掉 ... 这样服务端就不存任何东西了 ...

    不知道这种方式你能不能理解 ... 如果需要我可以写 DEMO ...
    zgq3337
    zgq3337
    Dec 27, 2019 via Android
    33
    用服务器累加器,加解密,IP 当 ID
    bitholic
    bitholic
    Dec 27, 2019
    34
    redis incr 很适用: https://redis.io/commands/INCR
    zoikhemlab
    zoikhemlab
    Dec 27, 2019
    35
    @fancy111 第一次就弹验证码,然后加,加到一百清空。
    optional
    optional
    Dec 27, 2019
    36
    服务端不存任何东西无法避免重放攻击,如果可以接受服务端存少量数据倒可以做的。
    aver4vex
    aver4vex
    Dec 27, 2019
    37
    cookie 里计数呗,既然不想服务端计数,那就浏览器计数咯。
    wangxiaoaer
    wangxiaoaer
    Dec 27, 2019
    38
    借楼问一下,假如接口正常返回的是二进制数据(比如 img 标签的图片地址),后台检测到频繁,准备前端弹验证码? 具体怎么弹? 在原本应该返回图片的这个接口返回验证码消息,然后前端配合处理界面显示? 这样感觉不合适啊。
    zchlwj
    zchlwj
    Dec 27, 2019
    39
    干嘛放数据库,redis 不香吗? HyperLogLog、布隆选择器,实现的方式多的很。
    seakingii
    seakingii
    Dec 27, 2019
    40
    肯定服务器计数啊,一个 IP 对应一个计数器,用 redis 或者自己写个使用内存存储计数的服务器,对付这种需求应该是压力很小啊.

    另外这种限制客户端请求的,也可以考虑回的方案,比如限制频率,比如一秒钟最多请求多少次,一分钟最多,一天最多来替代.这种解决方案可以使用 WEB SERVER 来解决,不用自己开发.
    zmqking
    zmqking
    Dec 27, 2019
    41
    Fingerprint.js 了解下。
    mconvert
    mconvert
    Dec 27, 2019
    42
    首先理解不了为啥会撑爆,第二就算存数据库不行,redis 存总也可以吧,key 是 ip,value 是 count
    no1xsyzy
    no1xsyzy
    Dec 27, 2019
    43
    @optional 我刚想说有办法,结果发现是我看错 “不存任何东西” 看成了 “不存在任何东西”(尴尬
    janxin
    janxin
    Dec 27, 2019
    44
    好奇你有多少 ip 访问,还能撑爆
    sessionreckon
    sessionreckon
    Dec 27, 2019
    45
    @Sunyanzi +1
    Cookie 里存下 counter + 时间戳 + 校验码就好了,类似 JWT 的验证流程
    Server 校验正确,每次操作增加 counter...
    这种方式不用 server 的缓存支持.减小出错可能性
    binux
    binux
    Dec 27, 2019 via Android
    46
    @Sunyanzi #31 不能挨重放,全靠客户端自律,和裸奔没区别。
    nonce 有不依赖服务器存储的实现方法?
    crab
    crab
    Dec 27, 2019
    47
    把 IP 时间次数记录到 url 请求参数里面,加密签名。
    wangyzj
    wangyzj
    Dec 27, 2019
    48
    Sunyanzi
    Sunyanzi
    Dec 27, 2019
    49
    @binux 「不严谨要求不能挨重放」 ... 双重否定 ... 可能是我措辞的问题比较容易看错 ...

    如果严谨要求不能挨重放的话只能服务端存 nonce ... 然后定期清理 ...
    yuansc
    yuansc
    Dec 27, 2019
    50
    多大数据量呀,,,DB 存不了,LRU Cache + 过期时间呗
    outerws
    outerws
    Dec 27, 2019
    51
    百度之类的防这种也不过是 cookie 带验证并借每几分钟更换一次 cookie 验证码防止跑接口?
    binux
    binux
    Dec 27, 2019 via Android
    53
    @Sunyanzi #47 「不严谨要求不能挨重放」 我的理解是「不严谨要求」下「不能挨重放」是可选项。
    而且如果存 nonce 我不觉得会比存 IP 少啊。。
    Sunyanzi
    Sunyanzi
    Dec 27, 2019
    54
    @binux 我描述的「不严谨要求不能挨重放」含义全等于「可以在有限范围内挨一些重放」 ...

    在这种情况下 ... nonce 不必需 ... 只要有 timestamp 控制 TTL 就好 ...

    如果要做请求唯一必须有 nonce 这个跑不了 ... 以及存 nonce 和存 IP 最核心的区别是含义不同 ...

    在存储层面 ... IP 是持久化存储 ... 除非有其他规则指定 IP 访问次数的清空规则 ... 否则就是增长慢总量大 ...

    nonce 是瞬时存储 ... 存 nonce 产生的数据会远超存 IP 的量 ... 但超时一般很短 ... 最多一小时就全清空了 ...
    lscho
    lscho
    Dec 27, 2019
    55
    数据库会爆?全中国所有人都来访问你的接口,数据库也爆不了。。。。
    stevenkang
    stevenkang
    Dec 27, 2019
    56
    如果通过服务器端记录客户端对应 ip 的请求次数,数据库会撑爆的
    >>>
    全球所有的 IPv4 加起来 42 亿个,全球总人口 70 多亿。

    到底是 70 多亿用户都来访问导致数据库跟不上呢,还是拥有 70 多亿用户的平台还搭建不出来支持 42 亿条记录的数据库?
    binux
    binux
    Dec 27, 2019 via Android
    57
    @Sunyanzi #52 LZ 问题是,每请求 100 次,弹出一次验证码。并没有时间限制,所以 once 也要一直存着。
    如果有时间限制,那一小时清一次 IP 也没问题啊。
    所以我说存哪个都差不多。
    maichael
    maichael
    Dec 27, 2019
    58
    @zmqking #40 fingerprint 也需要记下来,其实本质上和记 ip 没区别。
    zmqking
    zmqking
    Dec 27, 2019
    59
    @maichael 楼主已经说明是 IP 了,可能是出于安全问题还是怎么的。但这个不记录 IP。只是一个类似 token 的东西!理论上比判断 IP 更加准确,是从多个维度来计算得出的一个值!
    zhttty
    zhttty
    Dec 27, 2019❤️1
    60
    哪有那么麻烦:
    1、客户端存加密 cookie ;
    2、cookie 包含验证字段、UTC 时间戳、浏览次数、浏览器的一些识别信息、IP 信息(可选)等等;
    3、没有 cookie、cookie 无效、次数超标都弹验证码。
    westoy
    westoy
    Dec 27, 2019
    61
    服务端不做记录没用

    Cookie 计数的前提是客户端会更新 Cookie, 客户端会更新 Cookie 的前提是用会自动托管 Cookie 状态的类库, 问题是...........对方要只是很单纯的把抓到的 Cookie 放 header 里万年不变呢..........
    locoz
    locoz
    Dec 27, 2019 via Android
    62
    「客户端提交的一切信息都不可信」这一点都不知道吗…
    kisshere
    kisshere
    OP
    Dec 27, 2019
    63
    @zhttty 我上面都说了,你这个不可行,我每次都给服务器提供相同的加密 cookie,这个 cookie 是我抓包 chrome 得到的,你服务器每次都只能放行我通过
    ncwtf
    ncwtf
    Dec 27, 2019
    64
    这也不行那也不行?
    ip 放到缓存里加个失效时间有什么不行的。数据库撑爆。。一天访问一次,第一百天访问就要输入验证码?不得加个时间清除么。
    次数+时间+签名 加密放到 cookie 里有什么不行的,就算他伪造 cookie,时间到了不是也失效了?
    用 nginx 脚本应该也能实现需求啊


    小学生都能修改。。有丶牛逼。。
    eason1874
    eason1874
    Dec 27, 2019
    65
    严格限制请求次数就不可能不在服务器记录,无论什么 token 什么 nonce 都一样,纯粹依赖客户端的 nonce 摆脱不了重放。

    记录 100 次就重置了,这能有多少数据量,纯数据来说按 100 万个 token 算才不到 100mb
    markgor
    markgor
    Dec 27, 2019❤️2
    66
    “最终我还是采用简单粗暴的方法:rand(1,100)>98 && show_captcha();”

    看了下,你好像是想反爬,
    你這個方法我直接棄用這次請求,再發起新的請求,不就繞過去了嗎?
    rbuli
    rbuli
    Dec 27, 2019
    67
    ```
    public boolean flowControl(String ip) {
    String key = UrspReidsPrefixEnum.URSP_FREFIX.getCode() + UrspReidsPrefixEnum.FLOW_CONTROL_FREFIX.getCode() + ip;
    // 限流时间区间
    int seconds = 60;
    // 限流最大允许值
    int max = 100;
    long total = 1L;
    try {
    if (jedisClient.get(key) == null) {
    jedisClient.setex(key, "0", seconds);
    } else {
    total = jedisClient.incr(key).longValue();
    if (jedisClient.ttl(key).longValue() == -1L) {
    jedisClient.expire(key, seconds);
    }
    }
    } catch (Exception e) {
    LOGGER.error("流量控制组件:执行计数操作失败,无法执行计数");
    }
    long keytotaltransations = max;

    // 判断是否已超过最大值,超过则返回 false
    if (total > keytotaltransations) {
    return false;
    }
    return true;
    }
    ```
    xuanbg
    xuanbg
    Dec 27, 2019
    68
    后端完全不存数据,那这个需求臣妾办不到啊!

    但是,访问次数不存数据库可以存 Redis 嘛,key 设置一个合适的过期时间就行了。
    mlboy
    mlboy
    Dec 27, 2019 via iPhone
    69
    计数啊
    mlboy
    mlboy
    Dec 27, 2019 via iPhone
    70
    桶,令牌 了解一下
    jsnjfz
    jsnjfz
    Dec 27, 2019
    71
    nginx 里面写代码或许能搞
    zgq3337
    zgq3337
    Dec 27, 2019 via Android
    72
    为了防重放,再加一个总体 ID 轮链
    pinews
    pinews
    Dec 27, 2019
    73
    cookie 加密
    mreasonyang
    mreasonyang
    Dec 27, 2019 via iPhone
    74
    @wangxiaoaer header 里自定义字段或直接 403 就行了
    yankebupt
    yankebupt
    Dec 27, 2019
    75
    @kisshere
    发 100 个 token,99 层加密,每次请求解密一层返回下一个
    hardcode 验证 token 的逻辑不读库
    api limit rotate 复位的时候直接改下 hardcode
    然后上个防 replay attack 的防火墙(估计比内存数据库还贵,手动狗头)

    /\
    ||
    ||
    ||
    ……
    话说一般外行的话会不会都以为逻辑这样就可以的?
    yankebupt
    yankebupt
    Dec 27, 2019
    76
    如果去 TMD 用户体验的话,好像可以这样

    第一次使用之前,需要调用量申请 api 返回 ip+请求准确时间+定制 salt 静态加密的一个 token
    这个 token 十几秒(时间 1)就会过期,需要立刻使用,把后面请求的申请包在里面,同样返回静态验证过期时间不等(时间 2)的下次使用加密 token

    时间 2 可以宽松一点,一小时 60 次的话可以 2-15 分钟。

    服务器压力变大的时候缩短时间 2,然后把 spam 申请 api 的 ip 加入黑名单直接 ban 掉就是了。
    这样防重放防火墙逻辑可以把窗口只保留 2-15 分钟……带宽不大的话两分钟 D 不满数据库……

    记得之前一个外行还是不外行的站就是这么搞的,年代久远,想不起来是哪个了。
    yankebupt
    yankebupt
    Dec 28, 2019
    77
    @kisshere 当然如果和压力完全无关单纯只是想恶心人,可能你想要的是
    if (([minute of day] / [[typical interval of your api]) +- rand(md5(src-ip))*[random size] ) mod 100 >=98
    captcha()
    if fail
    add-ip-to-must-captcha-list-for-24h-until-success()
    fi
    fi
    happyz90
    happyz90
    Dec 28, 2019 via Android
    78
    不是精确的 100 次可以接受不?
    MoccaCafe
    MoccaCafe
    Dec 28, 2019 via iPhone
    79
    前端传来的数据是不可信任的,尤其是这种不带凭证又不带 ip,除非你对所有用户都做一样的限制
    hulonghe
    hulonghe
    Dec 28, 2019 via Android
    80
    开放授权码(后端限制授权码使用次数,合理控制分发),前后端都记录授权次数,校验对比。授权码生成得有规律,但又不可解,就像是 uuid。
    后端增加一个授权分发机制,如果达到异常点,控流,查来源,封访问权限(或者限制下次可使用时间)
    KuroNekoFan
    KuroNekoFan
    Dec 28, 2019 via iPhone
    81
    cookie 有个 http only 的属性
    KuroNekoFan
    KuroNekoFan
    Dec 28, 2019 via iPhone
    82
    不过也能从 dev panel 编辑,感觉可以从非对称加密的角度思考一下
    foamvalue
    foamvalue
    Dec 28, 2019
    83
    服务器缓存校验一下,redis 之类的。
    markgor
    markgor
    Dec 28, 2019
    84
    如果按標題來說,可否走 ws 協議?


    進入頁面->輸入驗證碼->建立 ws 通道->ws 判斷驗證碼-->正確執行返回並計次->達到 100 次重走輸入驗證碼流程。
    錯誤重來<-|
    zgq3337
    zgq3337
    Dec 28, 2019 via Android
    85
    以 URL 为数据库,服务器做加解密,为防无影,首次可能需要验证码
    encro
    encro
    Dec 30, 2019
    86
    redis hyperloglog 就是干这事的
    encro
    encro
    Dec 30, 2019
    87
    我这一个回答,至少值 1000RMB 吧
    encro
    encro
    Dec 30, 2019
    88
    只是不明白,
    一台普通 32G 服务器,
    将中国所有 IPV4 都存下来,
    也不会爆吧。

    谁来算以下?
    webcoder
    webcoder
    Dec 30, 2019❤️1
    89
    借用三楼的方案,第一次请求输入验证码,然后生成一个带专有及唯一属性(GUID+验证码+时间后 md5 之类的)的令牌(token),每次请求必须带上该令牌。令牌丢失就弹验证码。这就解决了身份验证问题。
    把令牌放到 redis 里记录请求次数,请求超限,令牌失效,重新输入验证码来生成新的令牌。
    不要把令牌放 session 里,因为如果你的请求量太大的话,令牌数估计也不少。所以尽量使用 redis 这种更专业一点的东西来处理。

    这个方案比你用 ip 记录更好,可以避免用代理伪造 ip 来刷接口。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2083 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 166ms · UTC 14:40 · PVG 22:40 · LAX 06:40 · JFK 09:40
    ♥ Do have faith in what you're doing.