验证码接口上线三天就被刷了,我后来加的几道防线
éªè¯ç æ¥å£ä¸çº¿ä¸å¤©å°±è¢«å·äºï¼æåæ¥å çå éé²çº¿
夿»©è¿ç»´ä¸å®¶ 2026-08-19 9 é 读6åéä¸ä¸ªæç»ä¸ä¸ªå°é¡¹ç®å äºææºéªè¯ç 注åãæ¥å£åå¥½ï¼æ¬å°æµéï¼é¨ç½²ä¸å»ï¼å两天没ä»ä¹äºã第ä¸å¤©æ©ä¸æå¼åéè®°å½ï¼åç¾å¤æ¡ï¼å ¨æ¯è¿å·ææºå·ï¼é´éä¸å°ä¸ç§ã
ä½é¢ç´æ¥æäºäºåå¤åã
è¿æååºè¿æ¥ââéªè¯ç æ¥å£å¤©çå°±æ¯å ¬ç½æ´é²çï¼ä½ æ»ä¸è½è®©ç¨æ·å ç»å½å注åãæ»å»è åªè¦ç¥éæ¥å£å°åï¼å©ä¸çå°±æ¯ä¸ä¸ª for 循ç¯çäºã
å çæ¸ æ¥å¯¹æ¹æä¹æç
ç¿»è¯·æ±æ¥å¿ï¼æ»å»æ¨¡å¼ç¹å«æ´ç´ ï¼
2026-07-18 03:14:22 POST /api/sms/send 138****0001 200 12ms 45.77.xx.xx
2026-07-18 03:14:22 POST /api/sms/send 138****0002 200 11ms 45.77.xx.xx
2026-07-18 03:14:23 POST /api/sms/send 138****0003 200 13ms 45.77.xx.xx
...
åä¸ä¸ª IPï¼User-Agent æ¯ python-requests/2.31.0ï¼è¿ä¼ªè£
齿å¾åãåç¾å¤æ¡è¯·æ±éä¸å¨åæ¨ä¸ç¹å°åç¹ä¹é´ï¼é£ä¼å¿è°ä¹æ²¡å¨çã
ä¸å¤æï¼ä½ææââçä¿¡æ¯ææ¡æ£è´¹çï¼å·ä¸æä¸å°±æ¯çéç½é¶ã
第ä¸éï¼æææºå·å IP åé¢çéå¶
æå è¡¥çæ¯æåºæ¬çéæµãä¸¤ä¸ªç»´åº¦åæ¶å¡ï¼
ææºå·ç»´åº¦ï¼åä¸ä¸ªå· 60 ç§å åªè½è¯·æ±ä¸æ¬¡ï¼24 å°æ¶ç´¯è®¡ä¸è¶ è¿ 5 次ã
from collections import defaultdict
import time
phone_records = defaultdict(list)
def check_phone_limit(phone: str) -> bool:
now = time.time()
# åªä¿ç 24 å°æ¶å
çè®°å½
phone_records[phone] = [t for t in phone_records[phone] if now - t < 86400]
records = phone_records[phone]
if records and now - records[-1] < 60:
return False
if len(records) >= 5:
return False
records.append(now)
return True
IP 维度ï¼å IP æ¯åéæå¤ 10 æ¬¡ï¼æ¯å°æ¶æå¤ 30 次ã
å¦æä½ ç¨ Redisï¼å¯ä»¥ç¨ INCR + EXPIRE æ¥åï¼æ¯å
ååå
¸é è°±ï¼
import redis
r = redis.Redis()
def check_ip_limit(ip: str) -> bool:
minute_key = f"sms:ip:{ip}:min"
hour_key = f"sms:ip:{ip}:hour"
if r.incr(minute_key) == 1:
r.expire(minute_key, 60)
if int(r.get(minute_key) or 0) > 10:
return False
if r.incr(hour_key) == 1:
r.expire(hour_key, 3600)
if int(r.get(hour_key) or 0) > 30:
return False
return True
å ä¸ä¹åï¼é£ç§å IP 循ç¯å·å·çèæ¬ç´æ¥å°±æ¦ä½äºãä½è¿äºæ²¡å 天忥䏿³¢ââè¿æ¬¡ç¨äºä»£çæ± ï¼æ¯æ¡è¯·æ±ç IP é½ä¸ä¸æ ·ï¼IP éæµæ²¡ç¨äºã
第äºéï¼äººæºéªè¯åç½®
ä»£çæ± å¯ä»¥æ¢ IPï¼ä½è¿äººæºéªè¯å°±è´¹å²äºãå¨å端å ä¸éæ»åéªè¯ï¼æè ç¹éãæè½¬ï¼ä»ä¹é½è¡ï¼ï¼éè¿ä¹åæ¿å°ä¸ä¸ªä¸æ¬¡æ§ tokenï¼è¿åææºå·ä¸èµ·æäº¤ç»å端ã
async function sendCode(phone) {
// ç¨æ·å
éè¿æ»åéªè¯
const token = await captcha.verify();
const res = await fetch('/api/sms/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, captcha_token: token })
});
return res.json();
}
å端æ¿å° captcha_token ä¹åå¿
é¡»å°éªè¯æå¡çæå¡ç«¯æ¥å£å»æ ¡éªï¼ä¸è½å¨å端 JS é夿ãæè§è¿æäººææ ¡éªé»è¾åå¨å端ââif (result.success) ç´æ¥æ¾è¡ââçäºæ²¡å ã
import requests
def verify_captcha(token: str) -> bool:
resp = requests.post('https://captcha-provider.com/verify', json={
'secret': CAPTCHA_SECRET,
'token': token
})
return resp.json().get('success', False)
å äºäººæºéªè¯ä¹åï¼çä¿¡æ¶èç«å»éåæ£å¸¸æ°´å¹³ã夿°èæ¬å°è¿ä¸æ¥å°±æ¾å¼äºââèªå¨è¿æ»åçææ¬è¿é«äºå·ä½ å æ¡çä¿¡çæ¶çã
第ä¸éï¼æå¡ç«¯ IP ç½åå
人æºéªè¯æ¦ä½äºä»æµè§å¨æ¹åæ¥çæ»å»ï¼ä½å¦ææäººæ¿å°äºä½ ççä¿¡æå¡åè¯ï¼æ¨¡æ¿ç¼ç æè API Keyï¼ï¼ç´æ¥ä»èªå·±çæå¡å¨è°çä¿¡ééæä¹åï¼
è¿æ¶å IP ç½ååå°±æç¨äºãæä½ ä¸å¡æå¡å¨çåºå£ IP é å°çä¿¡æå¡çç½ååéï¼åªæä»è¿äº IP ååºçè¯·æ±æè½è°éçä¿¡æ¥å£ï¼
# æ¥æå¡å¨åºå£ IP
curl -s ifconfig.me
# 120.78.xxx.xxx
æè¿ä¸ª IP å¡«å°çä¿¡æå¡åå°çç½ååé ç½®éã妿æå¤å°æå¡å¨æè ç»è¿äºè´è½½åè¡¡ï¼æææåºå£ IP é½å ä¸ã
è¿ä¸å±çæä¹å¨äºï¼å°±ç®åè¯æ³é²äºï¼æ»å»è æ¿å»ä¹è°ä¸éãèå¦æä½ åç°åè¯å¯è½æ³é²ï¼å¨å¹³å°ä¾§éç½®ä¸ä¸ç¼ç ï¼æ§çç«å»å¤±æã
ä¸ä¸ªå®¹æè¸©çåï¼éåé»è¾
éæµåå®ä¹åè¿æä¸ä¸ªç»èãç¨æ·ç¹äºåéï¼çäº 30 ç§æ²¡æ¶å°ï¼è¿è¥åæ ¢æè ç½ç»æå¨ï¼ï¼åç¹äºéåãå¦æç´æ¥çæä¸æ¡æ°éªè¯ç ååºå»ï¼ç¨æ·ææºä¸ä¼æ¶å°ä¸¤æ¡ä¸åçç ï¼ä¸ç¥é该è¾åªä¸ªã
æ¹æè¿æ ·ï¼60 ç§å éåæ¶ä¸çææ°ç ï¼æä¸ä¸æ¡åæ ·åå䏿¬¡ãéªè¯ç æææç»ä¸ 5 åéï¼è¿æä½åºã
code_store = {}
def get_or_create_code(phone: str) -> str:
now = time.time()
if phone in code_store:
code, ts = code_store[phone]
if now - ts < 300:
return code
new_code = f"{random.randint(100000, 999999)}"
code_store[phone] = (new_code, now)
return new_code
è¿æ ·ç¨æ·ä¸ç®¡ç¹å 次éåï¼æ¿å°ç齿¯åä¸ä¸ªç ï¼ä¸ä¼æ··ä¹±ã
å«å¿äºå 个å¼å¸¸æ¥è¦
鲿¤åäºå å±ä¹ä¸è½å®å ¨æ¾å¿ãå 两个æç®åçæ¥è¦ï¼
- æ¯å°æ¶çä¿¡åééè¶ è¿æ¥å¸¸åå¼ 3 åï¼åè¦
- å IP ä¸åéå 请æ±éªè¯ç è¶ è¿ 20 次ï¼åè¦å¹¶ä¸´æ¶å°ç¦
ä¸éè¦ä¸é¨æçæ§ç³»ç»ï¼ä¸ä¸ªå®æ¶æ¥æ¥å¿çèæ¬å°±å¤ãå ³é®æ¯åºäºé®é¢è½ç¬¬ä¸æ¶é´ç¥éï¼å«çæåºçè´¦åæåç°ã
å¦æä½ çä¸å¡æå¡å¨æ¬èº«å°±æ¥äºåè¦ééï¼ç´æ¥å¨éæµè§¦åæ¶å䏿¡éç¥å°±è¡ãæç¨çæ¯ä¸ä¸ª HTTP æ¥å£æ¨æ¶æ¯çæå¡ï¼éæµè¢«è§¦åçæ¶å POST ä¸ä¸å°±è½æ¶å°éç¥ï¼ä¸ç¨é¢å¤æä¸è¥¿ã
鲿¤å±çº§æ»ç»
æä¸é¢å éé²çº¿åä¸ä¸ï¼
| å±çº§ | ææ®µ | æ¦ä»ä¹ |
|---|---|---|
| å端 | 人æºéªè¯ï¼æ»å/ç¹éï¼ | èæ¬èªå¨åè¯·æ± |
| æ¥å£ | ææºå· + IP é¢çéå¶ | é«é¢å·å· |
| éé | IP ç½åå | åè¯æ³é²åçéæ³è°ç¨ |
| è¿è¥ | åééå¼å¸¸æ¥è¦ | æ¼ç½çå¼å¸¸æµé |
è¿å 屿²¡æåªä¸ªæ¯å¤é«æ·±çä¸è¥¿ï¼ä½ä¸åçè¯åæå¾å®å¨ââçä¿¡ééææ¡è®¡è´¹ï¼è¢«å·ä¸æä¸å¯è½å°±æ¯å¥½å ç¾åãä¸çº¿åè±å天æè¿äºå å¥½ï¼æ¯åºäºäºåè¡¥åç®å¾å¤ã
æèªå·±ç¨ççä¿¡æå¡èªå¸¦ IP ç½ååååè¯éç½®ï¼ä¸éè¦ä¼ä¸èµè´¨å°±è½æ¥å
¥ï¼æ¨¡æ¿ä¹æ¯å¹³å°æä¾çç°æçä¸ç¨å®¡æ ¸ãæéè¦çè¯å¯ä»¥ççï¼push.spug.ccã
Aitishiku.com