滑塊驗(yàn)證完整實(shí)現(xiàn)部署流程(前端?+?后端?+?Nginx?集成)
前言
滑塊驗(yàn)證的核心邏輯是:前端渲染滑塊+缺口背景圖,采集用戶滑動(dòng)軌跡;后端校驗(yàn)軌跡是否為真人行為(非機(jī)器勻速滑動(dòng)),驗(yàn)證通過后生成時(shí)效token;Nginx攔截業(yè)務(wù)請(qǐng)求,校驗(yàn)token有效性后放行。以下是可直接落地的完整方案,包含前端、后端、部署全流程。
一、核心原理
- 前端:生成隨機(jī)的背景圖+缺口,監(jiān)聽鼠標(biāo)/觸摸滑動(dòng)事件,采集滑動(dòng)軌跡(時(shí)間戳、X/Y坐標(biāo)、速度、加速度),滑動(dòng)完成后將軌跡和缺口偏移量傳給后端。
- 后端:校驗(yàn)軌跡特征(如滑動(dòng)時(shí)長(zhǎng)、速度波動(dòng)、是否勻速、缺口偏移匹配度),真人軌跡會(huì)有“先快后慢/輕微抖動(dòng)”,機(jī)器軌跡多為“勻速直線”;驗(yàn)證通過則生成短期有效token。
- Nginx:攔截業(yè)務(wù)請(qǐng)求,校驗(yàn)請(qǐng)求頭/Cookie中的驗(yàn)證token,有效則放行,無效則重定向到滑塊驗(yàn)證頁面。
二、完整實(shí)現(xiàn)步驟
步驟1:環(huán)境準(zhǔn)備
- 前端:無需框架,原生HTML+JS即可(也可適配Vue/React);
- 后端:Python 3.8+ + Flask + Pillow(生成驗(yàn)證圖) + Redis(存儲(chǔ)token/驗(yàn)證參數(shù));
- Nginx:確保包含
ngx_http_auth_request_module模塊(默認(rèn)編譯,nginx -V驗(yàn)證); - 依賴安裝:
# 后端依賴 pip install flask pillow redis requests # Redis(本地/云服務(wù)器,用于存儲(chǔ)驗(yàn)證參數(shù)和token) # 參考安裝:https://redis.io/docs/getting-started/installation/
步驟2:前端實(shí)現(xiàn)(滑塊渲染+軌跡采集)
新建slider.html,實(shí)現(xiàn)滑塊渲染、滑動(dòng)監(jiān)聽、軌跡采集和驗(yàn)證請(qǐng)求:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滑塊驗(yàn)證</title>
<style>
/* 滑塊容器樣式 */
.slider-container {
width: 320px;
height: 160px;
margin: 50px auto;
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.slider-bg {
width: 100%;
height: 120px;
position: relative;
border-radius: 4px;
overflow: hidden;
background: #f5f5f5;
}
.slider-gap {
position: absolute;
width: 40px;
height: 40px;
background: #fff;
border: 1px solid #e5e5e5;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
cursor: move;
/* 缺口位置由后端返回,初始隱藏 */
display: none;
}
.slider-bar {
width: 100%;
height: 30px;
background: #f8f8f8;
margin-top: 10px;
border-radius: 15px;
position: relative;
cursor: pointer;
}
.slider-btn {
width: 40px;
height: 30px;
background: #409eff;
border-radius: 15px;
position: absolute;
top: 0;
left: 0;
box-shadow: 0 0 5px rgba(64,158,255,0.5);
cursor: move;
text-align: center;
line-height: 30px;
color: #fff;
font-size: 12px;
}
.tips {
text-align: center;
margin-top: 10px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="slider-container">
<div class="slider-bg" id="sliderBg">
<div class="slider-gap" id="sliderGap"></div>
</div>
<div class="slider-bar" id="sliderBar">
<div class="slider-btn" id="sliderBtn">→</div>
</div>
<div class="tips" id="tips">請(qǐng)拖動(dòng)滑塊完成驗(yàn)證</div>
</div>
<script>
// 核心變量
const sliderBtn = document.getElementById('sliderBtn');
const sliderBar = document.getElementById('sliderBar');
const sliderGap = document.getElementById('sliderGap');
const sliderBg = document.getElementById('sliderBg');
const tips = document.getElementById('tips');
let startX = 0; // 滑動(dòng)起始X坐標(biāo)
let isDragging = false; // 是否正在滑動(dòng)
let track = []; // 滑動(dòng)軌跡:[{time: 時(shí)間戳, x: 坐標(biāo), y: 坐標(biāo)}]
let verifyId = ''; // 本次驗(yàn)證的唯一ID(后端生成)
let targetOffset = 0; // 目標(biāo)缺口偏移量(后端返回)
// 1. 初始化:從后端獲取驗(yàn)證圖和缺口參數(shù)
async function initVerify() {
try {
const res = await fetch('/api/slider/init');
const data = await res.json();
if (data.code === 200) {
verifyId = data.data.verifyId;
targetOffset = data.data.offset; // 目標(biāo)偏移量(像素)
// 設(shè)置背景圖
sliderBg.style.background = `url(${data.data.bgImg}) no-repeat center/contain`;
// 設(shè)置缺口位置
sliderGap.style.left = `${targetOffset}px`;
sliderGap.style.top = `${data.data.top}px`;
sliderGap.style.display = 'block';
tips.textContent = '請(qǐng)拖動(dòng)滑塊完成驗(yàn)證';
tips.style.color = '#666';
}
} catch (e) {
tips.textContent = '初始化失敗,請(qǐng)刷新重試';
tips.style.color = '#f56c6c';
console.error('初始化失敗:', e);
}
}
// 2. 采集滑動(dòng)軌跡(每10ms記錄一次坐標(biāo)和時(shí)間)
function recordTrack(x, y) {
track.push({
time: Date.now(),
x: x,
y: y
});
}
// 3. 滑動(dòng)結(jié)束:提交軌跡到后端驗(yàn)證
async function submitVerify() {
if (track.length < 5) { // 軌跡過短,判定為機(jī)器
tips.textContent = '驗(yàn)證失?。夯瑒?dòng)軌跡異常';
tips.style.color = '#f56c6c';
resetSlider();
return;
}
try {
const res = await fetch('/api/slider/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
verifyId: verifyId,
track: track, // 滑動(dòng)軌跡
finalOffset: sliderBtn.offsetLeft // 最終滑塊偏移量
})
});
const data = await res.json();
if (data.code === 200) {
// 驗(yàn)證通過:設(shè)置token到Cookie(供Nginx校驗(yàn))
document.cookie = `slider_token=${data.data.token}; max-age=300; path=/`;
tips.textContent = '驗(yàn)證通過!即將跳轉(zhuǎn)...';
tips.style.color = '#67c23a';
// 跳轉(zhuǎn)到業(yè)務(wù)頁面(或通知父頁面)
setTimeout(() => {
window.location.href = '/'; // 業(yè)務(wù)首頁
}, 1000);
} else {
tips.textContent = `驗(yàn)證失?。?{data.msg}`;
tips.style.color = '#f56c6c';
resetSlider();
// 重新初始化驗(yàn)證
setTimeout(initVerify, 1000);
}
} catch (e) {
tips.textContent = '驗(yàn)證請(qǐng)求失敗,請(qǐng)重試';
tips.style.color = '#f56c6c';
resetSlider();
console.error('驗(yàn)證提交失?。?, e);
}
}
// 4. 重置滑塊和軌跡
function resetSlider() {
sliderBtn.style.left = '0px';
isDragging = false;
track = [];
}
// 5. 綁定滑動(dòng)事件
// 鼠標(biāo)按下/觸摸開始
sliderBtn.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX - sliderBtn.offsetLeft;
// 開始記錄軌跡
recordTrack(sliderBtn.offsetLeft, e.clientY);
// 每10ms持續(xù)記錄軌跡
trackTimer = setInterval(() => {
if (isDragging) {
recordTrack(sliderBtn.offsetLeft, e.clientY);
}
}, 10);
});
// 觸摸適配(移動(dòng)端)
sliderBtn.addEventListener('touchstart', (e) => {
isDragging = true;
startX = e.touches[0].clientX - sliderBtn.offsetLeft;
recordTrack(sliderBtn.offsetLeft, e.touches[0].clientY);
trackTimer = setInterval(() => {
if (isDragging) {
recordTrack(sliderBtn.offsetLeft, e.touches[0].clientY);
}
}, 10);
});
// 鼠標(biāo)移動(dòng)/觸摸移動(dòng)
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const newX = e.clientX - startX;
// 限制滑塊范圍:0 ~ 滑塊條寬度 - 滑塊寬度
const maxX = sliderBar.offsetWidth - sliderBtn.offsetWidth;
const finalX = Math.max(0, Math.min(newX, maxX));
sliderBtn.style.left = `${finalX}px`;
});
document.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const newX = e.touches[0].clientX - startX;
const maxX = sliderBar.offsetWidth - sliderBtn.offsetWidth;
const finalX = Math.max(0, Math.min(newX, maxX));
sliderBtn.style.left = `${finalX}px`;
});
// 鼠標(biāo)松開/觸摸結(jié)束
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
clearInterval(trackTimer);
submitVerify(); // 提交驗(yàn)證
});
document.addEventListener('touchend', () => {
if (!isDragging) return;
isDragging = false;
clearInterval(trackTimer);
submitVerify();
});
// 頁面加載時(shí)初始化驗(yàn)證
window.onload = initVerify;
</script>
</body>
</html>
步驟3:后端實(shí)現(xiàn)(驗(yàn)證圖生成+軌跡校驗(yàn))
新建slider_server.py,實(shí)現(xiàn)3個(gè)核心接口:初始化驗(yàn)證(生成背景圖/缺口)、校驗(yàn)軌跡、驗(yàn)證token(供Nginx調(diào)用):
import os
import uuid
import random
import time
import json
from PIL import Image, ImageDraw
from flask import Flask, jsonify, request, send_file, make_response
import redis
import hashlib
app = Flask(__name__)
# Redis配置(存儲(chǔ)驗(yàn)證參數(shù)和token,過期時(shí)間5分鐘)
redis_client = redis.Redis(
host='127.0.0.1',
port=6379,
db=0,
decode_responses=True,
password='' # 如有密碼請(qǐng)?zhí)顚?
)
# 配置:驗(yàn)證圖存儲(chǔ)路徑(臨時(shí))
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
IMG_DIR = os.path.join(BASE_DIR, 'slider_imgs')
if not os.path.exists(IMG_DIR):
os.makedirs(IMG_DIR)
# -------------------------- 核心工具函數(shù) --------------------------
# 1. 生成隨機(jī)驗(yàn)證圖(帶缺口)
def generate_slider_img():
# 生成背景圖(隨機(jī)顏色+隨機(jī)線條,模擬真實(shí)圖片)
width, height = 300, 100 # 背景圖尺寸
bg_img = Image.new('RGB', (width, height), (random.randint(230, 255), random.randint(230, 255), random.randint(230, 255)))
draw = ImageDraw.Draw(bg_img)
# 畫隨機(jī)線條(增加干擾)
for _ in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=(random.randint(100, 200), random.randint(100, 200), random.randint(100, 200)), width=2)
# 生成缺口(隨機(jī)位置:X軸 80~220,Y軸 30~70)
gap_width, gap_height = 40, 40
gap_x = random.randint(80, 220) # 缺口X偏移(目標(biāo)偏移量)
gap_y = random.randint(30, 70) # 缺口Y偏移
# 畫缺口(白色矩形,模擬缺失)
draw.rectangle((gap_x, gap_y, gap_x+gap_width, gap_y+gap_height), fill=(255, 255, 255))
# 保存背景圖
img_name = f'{uuid.uuid4()}.png'
img_path = os.path.join(IMG_DIR, img_name)
bg_img.save(img_path)
return {
'img_path': img_path,
'img_name': img_name,
'offset': gap_x, # 缺口X偏移量(目標(biāo)值)
'top': gap_y # 缺口Y偏移量
}
# 2. 校驗(yàn)滑動(dòng)軌跡(核心:區(qū)分真人/機(jī)器)
def check_track(track, final_offset, target_offset):
"""
:param track: 滑動(dòng)軌跡列表 [{time, x, y}]
:param final_offset: 用戶最終滑動(dòng)的偏移量
:param target_offset: 目標(biāo)缺口偏移量
:return: (是否通過, 失敗原因)
"""
# 1. 偏移量校驗(yàn):誤差±5像素內(nèi)
offset_error = abs(final_offset - target_offset)
if offset_error > 5:
return False, f'偏移量錯(cuò)誤(目標(biāo){target_offset},實(shí)際{final_offset})'
# 2. 軌跡長(zhǎng)度校驗(yàn):至少5個(gè)點(diǎn)(避免瞬間滑動(dòng))
if len(track) < 5:
return False, '軌跡過短'
# 3. 滑動(dòng)時(shí)長(zhǎng)校驗(yàn):0.5~3秒(真人滑動(dòng)不會(huì)太快/太慢)
total_time = track[-1]['time'] - track[0]['time']
total_time_s = total_time / 1000
if total_time_s < 0.5 or total_time_s > 3:
return False, f'滑動(dòng)時(shí)長(zhǎng)異常({total_time_s:.2f}秒)'
# 4. 速度波動(dòng)校驗(yàn):真人速度有波動(dòng),機(jī)器多勻速
speeds = []
for i in range(1, len(track)):
time_diff = track[i]['time'] - track[i-1]['time']
x_diff = track[i]['x'] - track[i-1]['x']
if time_diff == 0:
continue
speed = x_diff / time_diff # 像素/毫秒
speeds.append(speed)
# 計(jì)算速度標(biāo)準(zhǔn)差(波動(dòng)值):<0.01 判定為勻速(機(jī)器)
if len(speeds) < 3:
return False, '軌跡點(diǎn)數(shù)不足'
avg_speed = sum(speeds) / len(speeds)
std_speed = (sum([(s - avg_speed)**2 for s in speeds]) / len(speeds)) ** 0.5
if std_speed < 0.01:
return False, '滑動(dòng)速度勻速(疑似機(jī)器)'
# 5. 軌跡Y軸校驗(yàn):真人滑動(dòng)Y軸有輕微波動(dòng),機(jī)器Y軸固定
y_values = [p['y'] for p in track]
y_max = max(y_values)
y_min = min(y_values)
if y_max - y_min < 2:
return False, 'Y軸無波動(dòng)(疑似機(jī)器)'
return True, '驗(yàn)證通過'
# 3. 生成驗(yàn)證token(供Nginx校驗(yàn))
def generate_token(verify_id):
token = hashlib.md5(f'{verify_id}_{int(time.time())}_slider_secret'.encode()).hexdigest()
# 存儲(chǔ)token到Redis,過期5分鐘
redis_client.setex(f'slider_token:{token}', 300, 'valid')
return token
# -------------------------- 接口實(shí)現(xiàn) --------------------------
# 1. 初始化驗(yàn)證接口(生成背景圖+缺口參數(shù))
@app.route('/api/slider/init', methods=['GET'])
def init_slider():
try:
# 生成驗(yàn)證圖
img_info = generate_slider_img()
# 生成唯一驗(yàn)證ID
verify_id = str(uuid.uuid4())
# 存儲(chǔ)驗(yàn)證參數(shù)到Redis(過期5分鐘)
redis_client.setex(
f'slider_verify:{verify_id}',
300,
json.dumps({
'offset': img_info['offset'],
'top': img_info['top'],
'img_name': img_info['img_name']
})
)
# 返回結(jié)果(圖片路徑為訪問路徑)
return jsonify({
'code': 200,
'msg': '初始化成功',
'data': {
'verifyId': verify_id,
'offset': img_info['offset'],
'top': img_info['top'],
'bgImg': f'/api/slider/img/{img_info["img_name"]}' # 圖片訪問接口
}
})
except Exception as e:
return jsonify({'code': 500, 'msg': f'初始化失?。簕str(e)}'}), 500
# 2. 驗(yàn)證圖片訪問接口
@app.route('/api/slider/img/<img_name>', methods=['GET'])
def get_slider_img(img_name):
img_path = os.path.join(IMG_DIR, img_name)
if not os.path.exists(img_path):
return jsonify({'code': 404, 'msg': '圖片不存在'}), 404
# 返回圖片,并設(shè)置緩存(短期)
response = make_response(send_file(img_path, mimetype='image/png'))
response.headers['Cache-Control'] = 'max-age=300'
return response
# 3. 滑塊驗(yàn)證接口(校驗(yàn)軌跡)
@app.route('/api/slider/verify', methods=['POST'])
def verify_slider():
try:
data = request.get_json()
verify_id = data.get('verifyId')
track = data.get('track', [])
final_offset = data.get('finalOffset', 0)
# 1. 校驗(yàn)參數(shù)
if not verify_id or not track or final_offset is None:
return jsonify({'code': 400, 'msg': '參數(shù)缺失'}), 400
# 2. 獲取Redis中的驗(yàn)證參數(shù)
verify_info_str = redis_client.get(f'slider_verify:{verify_id}')
if not verify_info_str:
return jsonify({'code': 400, 'msg': '驗(yàn)證已過期,請(qǐng)刷新'}), 400
verify_info = json.loads(verify_info_str)
target_offset = verify_info['offset']
# 3. 校驗(yàn)軌跡
is_pass, msg = check_track(track, final_offset, target_offset)
if not is_pass:
return jsonify({'code': 403, 'msg': msg}), 403
# 4. 驗(yàn)證通過:生成token,刪除驗(yàn)證參數(shù)(防止復(fù)用)
token = generate_token(verify_id)
redis_client.delete(f'slider_verify:{verify_id}')
return jsonify({
'code': 200,
'msg': '驗(yàn)證通過',
'data': {'token': token}
})
except Exception as e:
return jsonify({'code': 500, 'msg': f'驗(yàn)證失?。簕str(e)}'}), 500
# 4. Nginx校驗(yàn)token接口(內(nèi)部調(diào)用)
@app.route('/api/slider/check_token', methods=['GET'])
def check_token():
# 從Cookie獲取token
token = request.cookies.get('slider_token')
if not token:
return '', 401 # 無token,驗(yàn)證失敗
# 校驗(yàn)token是否有效
is_valid = redis_client.get(f'slider_token:{token}')
if is_valid == 'valid':
# 驗(yàn)證通過,刪除token(防止復(fù)用)
redis_client.delete(f'slider_token:{token}')
return '', 200
else:
return '', 401 # token無效
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=False)
步驟4:Nginx配置(攔截請(qǐng)求+校驗(yàn)token)
修改nginx.conf,實(shí)現(xiàn)“攔截業(yè)務(wù)請(qǐng)求→校驗(yàn)滑塊token→放行/重定向”:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 后端滑塊驗(yàn)證服務(wù)
upstream slider_server {
server 127.0.0.1:5001;
}
server {
listen 80;
server_name localhost;
# 業(yè)務(wù)根路徑(需要驗(yàn)證的路徑)
location / {
# 1. 校驗(yàn)滑塊token(內(nèi)部子請(qǐng)求)
auth_request /api/slider/check_token;
# 2. token無效/缺失 → 重定向到滑塊驗(yàn)證頁面
error_page 401 = @redirect_slider;
# 3. token有效 → 放行到業(yè)務(wù)服務(wù)(替換為你的實(shí)際業(yè)務(wù)地址)
proxy_pass http://127.0.0.1:8080; # 你的業(yè)務(wù)服務(wù)地址
}
# 重定向到滑塊驗(yàn)證頁面
location @redirect_slider {
rewrite ^/(.*)$ /slider.html permanent;
}
# 滑塊驗(yàn)證相關(guān)接口 → 轉(zhuǎn)發(fā)到后端服務(wù)
location /api/slider/ {
proxy_pass http://slider_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 滑塊驗(yàn)證頁面(靜態(tài)文件)
location /slider.html {
root /path/to/your/static/files; # 替換為slider.html所在目錄
expires 0; # 禁止緩存
}
# Nginx內(nèi)部調(diào)用的token校驗(yàn)接口(禁止外部訪問)
location = /api/slider/check_token {
internal;
proxy_pass http://slider_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
步驟5:?jiǎn)?dòng)與測(cè)試
- 啟動(dòng)Redis:
redis-server(確保端口6379); - 啟動(dòng)后端服務(wù):
python slider_server.py; - 放置前端文件:將
slider.html放到Nginx配置的靜態(tài)文件目錄; - 重啟Nginx:
nginx -s reload; - 測(cè)試:訪問
http://localhost→ 自動(dòng)重定向到滑塊驗(yàn)證頁面 → 拖動(dòng)滑塊完成驗(yàn)證 → 驗(yàn)證通過后跳轉(zhuǎn)到業(yè)務(wù)頁面。
三、防破解優(yōu)化(關(guān)鍵!避免被機(jī)器破解)
1. 前端優(yōu)化
- 軌跡加密:將軌跡數(shù)據(jù)用AES加密后傳輸(避免抓包篡改);
- 混淆JS:對(duì)滑動(dòng)事件的JS代碼進(jìn)行混淆(防止逆向分析);
- 禁用模擬:檢測(cè)是否為模擬器/自動(dòng)化工具(如檢測(cè)
webdriver標(biāo)識(shí)); - 動(dòng)態(tài)樣式:滑塊樣式隨機(jī)變化(顏色、大小、形狀),避免固定模板。
2. 后端優(yōu)化
- 動(dòng)態(tài)閾值:根據(jù)IP/設(shè)備調(diào)整校驗(yàn)閾值(如高頻驗(yàn)證的IP提高校驗(yàn)嚴(yán)格度);
- 防重放:驗(yàn)證ID僅能使用一次,校驗(yàn)后立即刪除;
- 圖片增強(qiáng):驗(yàn)證圖加入隨機(jī)水印、扭曲、噪點(diǎn)(防止圖像識(shí)別破解);
- 風(fēng)控結(jié)合:結(jié)合IP黑名單、設(shè)備指紋(如瀏覽器指紋)、訪問頻率限制。
3. 部署優(yōu)化
- 頻率限制:Nginx配置
limit_req_module限制驗(yàn)證接口請(qǐng)求頻率(如每秒1次);limit_req_zone $binary_remote_addr zone=slider:10m rate=1r/s; location /api/slider/ { limit_req zone=slider burst=2 nodelay; proxy_pass http://slider_server; } - 分布式部署:Redis使用集群,支持多實(shí)例后端服務(wù);
- 日志監(jiān)控:記錄驗(yàn)證失敗日志,分析異常IP/設(shè)備,及時(shí)調(diào)整策略。
四、擴(kuò)展適配
1. 移動(dòng)端適配
- 前端已兼容
touch事件,只需調(diào)整樣式適配移動(dòng)端屏幕; - 優(yōu)化滑塊大?。ㄈ鐚挾?80px,適配手機(jī)屏幕)。
2. 集成第三方滑塊(簡(jiǎn)化開發(fā))
如果不想自研,可直接集成成熟的第三方滑塊:
- 極驗(yàn)(GEETEST):https://www.geetest.com/ (文檔完善,支持私有化部署);
- 頂象:https://www.dingxiang-inc.com/ (風(fēng)控能力強(qiáng),適合高安全場(chǎng)景);
- 集成方式:替換前端滑塊代碼為第三方SDK,后端調(diào)用第三方校驗(yàn)接口即可。
五、常見問題排查
- 驗(yàn)證圖無法顯示:檢查
IMG_DIR路徑是否正確,Nginx是否有權(quán)限訪問圖片目錄; - 軌跡校驗(yàn)失敗:調(diào)整
check_track函數(shù)的閾值(如速度標(biāo)準(zhǔn)差、滑動(dòng)時(shí)長(zhǎng)); - Redis連接失敗:檢查Redis地址、端口、密碼是否正確,確保Redis服務(wù)運(yùn)行;
- Nginx重定向循環(huán):確保
/api/slider/接口不被auth_request攔截(Nginx配置中排除)。
總結(jié)
滑塊驗(yàn)證的核心是軌跡特征校驗(yàn)(區(qū)分真人/機(jī)器),而非單純的偏移量匹配。自研方案適合中小場(chǎng)景,若追求更高安全性/更低開發(fā)成本,建議集成極驗(yàn)、頂象等第三方滑塊服務(wù)。實(shí)際部署時(shí),需結(jié)合風(fēng)控策略(IP、設(shè)備、頻率),構(gòu)建多層防御體系,平衡安全與用戶體驗(yàn)。
到此這篇關(guān)于滑塊驗(yàn)證完整實(shí)現(xiàn)部署流程的文章就介紹到這了,更多相關(guān)前端+后端+Nginx集成實(shí)現(xiàn)滑塊驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nginx之正向代理與反向代理進(jìn)階方式(支持https)
文章介紹了如何在Nginx中實(shí)現(xiàn)正向代理和反向代理對(duì)HTTP和HTTPS協(xié)議的支持,通過使用第三方模塊`ngx_http_proxy_connect_module`和Nginx內(nèi)置的`ngx_http_ssl_module`,可以實(shí)現(xiàn)Nginx對(duì)HTTPS的正向和反向代理2025-03-03
nginx多l(xiāng)ocation配置實(shí)例代碼
公司測(cè)試環(huán)境使用nginx部署多個(gè)前端項(xiàng)目,下面這篇文章主要給大家介紹了關(guān)于nginx多l(xiāng)ocation配置的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
nginx+lua+redis實(shí)現(xiàn)降級(jí)的示例代碼
隨著用戶訪問量的激增,網(wǎng)站或電商平臺(tái)可能會(huì)面臨系統(tǒng)超負(fù)載的問題,導(dǎo)致注冊(cè)、下單、支付等功能出現(xiàn)問題,為保障核心服務(wù)的高可用性,可以采用降級(jí)策略,本文就來介紹一下nginx+lua+redis降級(jí),感興趣的可以了解學(xué)習(xí)2024-10-10
解決httpd占用80端口導(dǎo)致Nginx啟動(dòng)失敗報(bào)錯(cuò)的解決辦法
今天在建自己小網(wǎng)站時(shí)啟動(dòng)Nginx時(shí),發(fā)現(xiàn)其報(bào)下列錯(cuò)誤,意思是因?yàn)?0端口被占用導(dǎo)致Nginx啟動(dòng)失敗,所以本文小編給大家介紹介紹如何解決解決httpd占用80端口導(dǎo)致Nginx啟動(dòng)不成功報(bào)nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)2023-11-11
利用nginx搭建靜態(tài)資源服務(wù)器的方法步驟
這篇文章主要介紹了利用nginx搭建靜態(tài)資源服務(wù)器的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

