ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

网易易盾滑块验证DEMO全流程解析:从初始化到后端二次校验

网易易盾滑块验证DEMO全流程解析:从初始化到后端二次校验 简介网易易盾滑块验证演示是一套面向网页开发者与验证码技术学习者的源码示例集中演示了网易易盾滑块验证码的核心机制涵盖拼图拖动、随机图像生成、滑块起始与最终位置的后端比对等关键步骤并加入连续尝试次数限制、验证码过期时间等安全策略帮助理解真实场景中验证码如何抵御自动化工具与机器人攻击。包体极轻量仅2KB共3个文件以两个脚本为主体一个承载滑块渲染、用户交互处理及与服务器的通信逻辑另一个作为设置文件存放服务器地址、密钥、错误重试次数等可调参数另有联系方式说明文件。目前已有2252人学习适合希望快速接入易盾滑块验证或对验证码防破解设计感兴趣的开发者通过阅读该演示可掌握动态图片生成、坐标计算、服务端校验等实现细节并能直接修改配置项扩展自定义规则作为实际项目集成或安全测试的实用参考。1. 网易易盾滑块验证的DEMO在验证什么拿到一个网易易盾滑块验证的 demo 程序多数人的第一反应是研究怎么把背景图上的缺口找准。做过对接就会知道缺口识别只是整条链路里最不重要的环节易盾真正在意的不是“你找没找对缺口”而是“你这一串拖动轨迹像不像真人、你手里有没有拿到合法的验证凭证”。一个能用的 DEMO价值在于把初始化、用户滑动、前端拿 token、后端二次校验这条链路完整跑通而不是把一张图抠出来。这套东西适合谁要在登录页里接入人机校验的前端工程师、需要在数据采集场景里处理登录态验证的脚本工程师以及想搞懂验证码服务端校验逻辑的测试开发。这篇就用一个最小 DEMO 把整条流水线讲清楚。2. 滑块验证的最小闭环初始化、拖动、二次校验2.1 请求时序前端只过第一关后端必须再过一遍网易易盾滑块验证的完整链路比表面上看到的“拖一下弹窗消失”要长得多。用户拖完滑块后弹窗关闭只是通过了前端这一关业务后端如果只依赖前端回调就放行登录等于没接验证码。一次完整的校验分成三段第一段是初始化。前端带着易盾后台分配的 captchaId有些文档里也叫 appId去请求验证码服务拿到背景图、滑块图和一个初始 token。这个 token 代表“一次验证会话”它和用户名、密码没有任何关系只用来标识这次验证。第二段是拖动与行为采集。用户从起点拖到缺口附近前端把坐标轨迹、耗时、加速度等行为数据连同 token 回传给易盾服务端。易盾根据这套轨迹判断“这是人还是机器”判定通过后返回一个 validate 字段。到这一步前端工作结束。第三段是二次校验。业务前端把 token 和 validate 放进登录请求业务后端拿到后再通过服务端接口把这些参数发给易盾。易盾用 secretId/secretKey 签名确认请求来源合法返回本次校验的真伪结果。只有这一段的返回为真业务系统才能认定这次登录是真人操作。DEMO 里最常见的错误是只做了前两段前端回调成功就把登录表单提交了后端没调校验接口。这样等于把验证码变成了一个前端装饰品攻击者直接改写前端回调数据即可绕过。2.2 token、validate、secretId 各管一段别混着用把字段角色理清楚DEMO 写起来才不会绕晕。下面是这套链路里最核心的几个字段以及它们各自的归属方字段产生方使用方作用captchaId易盾后台申请前端初始化参数标识当前接入的是哪个业务token易盾服务端返回给前端前端持有随登录请求传给业务后端标记一次验证会话validate易盾在滑块通过后返回给前端前端传给业务后端后端再传给易盾二次校验的凭证secretId易盾后台申请业务后端持有标识服务端调用方的身份secretKey易盾后台申请业务后端持有对校验请求做签名注意 secretKey 绝对不能出现在前端代码里。DEMO 阶段经常有人图省事把 secretKey 写进一个 config.js 供前端调用这相当于把服务端校验的通行证贴在了门外面。标准做法是后端单独保存通过环境变量或配置中心注入。token 和 validate 这两个字段还有一个容易混淆的点token 是验证会话的编号validate 是本次会话通过后颁发的凭证两者是成对出现的。后端校验时如果漏传了 validate或者把 token 当成 validate 传进去服务端会直接拒绝。2.3 后端校验接口的最小实现Flask 版后端二次校验是 DEMO 中最容易偷工减料的部分。这里给一个 Flask 实现把签名、参数组包、远端校验都放在一个接口里import hmac import hashlib import time import requests from flask import Flask, request, jsonify app Flask(__name__) SECRET_ID your_secret_id # 易盾后台申请放环境变量里 SECRET_KEY your_secret_key # 只允许后端持有 CHECK_URL https://open.dun.163.com/api/check # 实际地址以易盾接入文档为准 def make_sign(params: dict, secret_key: str) - str: items sorted(params.items()) raw .join(f{k}{v} for k, v in items if v not in (, None)) return hmac.new(secret_key.encode(), raw.encode(), hashlib.sha256).hexdigest() app.route(/auth/captcha-verify, methods[POST]) def captcha_verify(): payload request.json or {} token payload.get(token) validate payload.get(validate) if not token or not validate: return jsonify({pass: False, reason: token or validate missing}), 400 params { secretId: SECRET_ID, captchaId: payload.get(captchaId), token: token, validate: validate, timestamp: int(time.time() * 1000), nonce: payload.get(nonce, ), data: payload.get(userData, ), } params[signature] make_sign(params, SECRET_KEY) try: resp requests.post(CHECK_URL, dataparams, timeout5).json() except requests.exceptions.Timeout: return jsonify({pass: False, reason: upstream timeout}), 504 return jsonify({pass: resp.get(result) is True, detail: resp.get(error, )}) if __name__ __main__: app.run(port5001)这段代码里需要注意三件事。第一签名算法做了参数名升序排序然后拼成 keyvalue 的串再做 HMAC-SHA256这是服务端校验接口最常见的签名套路实际参数名和拼接规则要以易盾服务端文档为准。第二timeout5是必须的远端校验接口如果响应慢不能无限占着登录线程。第三result为 true 才放行其他情况都要进入失败分支包括接口返回错误码、返回空值、网络超时三种情况。2.4 前端 DEMO 的最小页面initCaptcha 回调拿 validate前端部分用一个静态 HTML 就能讲清楚。使用易盾标准的加载脚本在页面里指定一个容器初始化时把 captchaId 传进去!DOCTYPE html html langzh head meta charsetUTF-8 title网易易盾滑块验证 DEMO/title /head body form idlogin-form input nameusername placeholder账号 input namepassword typepassword placeholder密码 div idcaptcha-box/div input typehidden nametoken idtoken-field input typehidden namevalidate idvalidate-field button typesubmit登录/button /form script srchttps://cstaticdun.126.net/load.min.js/script script initCaptcha({ captchaId: 你的captchaId, element: #captcha-box, mode: slide, enableIce: false, protocol: https, width: 320, success: function (data) { document.getElementById(token-field).value data.token; document.getElementById(validate-field).value data.validate; } }); /script /body /htmlmode: slide指定渲染为滑块验证enableIce: false表示关闭无感验证模式这样 DEMO 里每次都会走完整的滑动流程方便观察数据流转。success回调是所有环节的入口只有用户完成有效拖动后才会触发。把 data.token 和 data.validate 同时写进两个隐藏字段是为了让后面的表单提交逻辑不用关心验证码是怎么工作的只需要从隐藏字段里取值。需要注意加载脚本地址和 captchaId 都需要替换成自己开通服务后拿到的真实值。这个最小页面没有处理验证码初始化失败的情况生产环境里还要监听error回调比如网络被拦截或页面协议与加载地址不一致时给出提示。3. 在本地把 demo 程序跑通模拟登录一条链路3.1 本地工程的目录结构与启动顺序这个 demo 程序在本地需要同时起两个服务前端静态页和一个 Flask 后端。目录结构保持简单captcha-demo/ ├── server/ │ ├── app.py # Flask 后端负责登录接口和二次校验 │ └── requirements.txt └── web/ ├── index.html # 登录页 └── captcha.js # 易盾初始化与表单提交逻辑启动后端cd captcha-demo/server pip install flask requests flask-cors python app.py再开一个终端用 Python 自带的静态文件服务承载前端页面cd captcha-demo/web python3 -m http.server 8000浏览器访问http://localhost:8000就能看到登录页。这里有一个绕不开的问题前端页面跑在 8000 端口后端校验接口跑在 5001 端口属于跨域请求。在 app.py 里加 CORS 支持限制只允许本地页面访问from flask_cors import CORS CORS(app, resources{ r/auth/*: {origins: http://localhost:8000} })origins没有写成*是因为服务端校验接口不应该对任意站点开放否则别人直接拿你的接口当验证码代理用了。本地调试按域名精确放行部署后换成正式域名或者直接改成同域部署。3.2 把验证结果绑进登录表单模拟带滑块验证的登录页很多登录页并不是表单直接提交而是前端拦截 submit 事件后改成异步请求。这里的关键在于提交动作发生前要检查 validate 字段有没有值如果没有值说明用户根本没完成滑块验证直接提交会被后端拒绝。const form document.getElementById(login-form); form.addEventListener(submit, async (e) { e.preventDefault(); const validate document.getElementById(validate-field).value; if (!validate) { alert(请先完成滑块验证); return; } const resp await fetch(/auth/redirect-login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: form.username.value, password: form.password.value, captchaId: 你的captchaId, token: document.getElementById(token-field).value, validate: validate }) }); const result await resp.json(); console.log(登录结果, result); });注意fetch的路径是相对路径本地调试时页面在 8000 端口请求会发到 8000 端口的/auth/redirect-login。要让这条链路在本地完整跑通要么在静态服务里配置代理转发要么直接把/auth/redirect-login的完整地址改成http://localhost:5001/auth/redirect-login。更推荐前者因为改完整地址意味着前端代码里写死了环境信息换到测试环境就又要改一遍。这个页面模拟的就是“带滑块验证的登录页面如何模拟登录”里最关键的一个习惯把验证码结果作为登录请求的普通参数一起提交而不是让验证码逻辑独立于登录流程之外。3.3 用 requests 重放登录链路浏览器完成滑块脚本完成登录如果目的是写一个自动化的登录脚本最常见的坑是尝试完全用 requests 去模拟易盾的前端接口。实际做下来会发现易盾的加载接口本身带有风控策略直接脚本模拟很容易触发二次验证甚至拿不到可用的验证码数据。一个更可靠的 DEMO 方案是浏览器里完成滑块脚本负责登录。操作步骤是先在控制台注入一段监听代码window.__captchaData null; initCaptcha({ captchaId: 你的captchaId, element: #captcha-box, mode: slide, success: function (data) { window.__captchaData data; } });手动拖完滑块后在控制台执行copy(window.__captchaData)拿到 token 和 validate。然后用 requests 脚本继续跑登录import requests session requests.Session() # 1. 先访问登录页种下 cookie session.get(http://localhost:8000) # 2. 从浏览器控制台粘贴上一步拿到的数据 token 从控制台copy出来的token validate 从控制台copy出来的validate # 3. 调业务后端登录接口后端内部会再调易盾二次校验 resp session.post( http://localhost:5001/auth/redirect-login, json{ username: tester, password: 123456, captchaId: 你的captchaId, token: token, validate: validate, }, timeout5, ) print(resp.json())这套“浏览器半自动 脚本请求重放”的方式本质上是把易盾前端的行为采集和风控判断交给真实浏览器环境自己只负责拿到最终凭证后继续业务请求。它适合所有需要登录态的场景比如联调环境里自动化跑接口测试或者验证后端二次校验逻辑是否正常工作。不要试图用 requests 去逆向易盾的轨迹上报协议那是一条投入产出比极低的路而且一旦触发风控会把你整个出口 IP 都拉黑。4. 轨迹、缺口与回调滑块验证 DEMO 的三个调试点4.1 拖动轨迹要带“人类特征”匀速直线最容易露馅易盾对拖动轨迹的判断重点不在于起点和终点是否对齐缺口而在于中间过程的时序特征。真实人类拖动鼠标时手部会有微小的抖动轨迹不是一条完美直线速度曲线呈现“加速—匀速—减速—校正”的形态。匀速直线运动的轨迹在行为数据里极其显眼。如果 DEMO 里需要生成一段用于联调的测试轨迹可以用下面这个简单的生成器import random def human_track(distance, speed0.8): track [] cur 0.0 t 0.0 # 起始迟疑先停顿一小段 t random.uniform(0.1, 0.3) # 前 20% 距离加速 while cur distance * 0.2: step random.uniform(1, 3) * speed cur min(cur step, distance * 0.2) track.append((round(cur, 1), round(t, 3))) t random.uniform(0.015, 0.03) # 中段保持速度但每一步有小幅变化 while cur distance * 0.75: step random.uniform(2, 5) * speed cur min(cur step, distance * 0.75) track.append((round(cur, 1), round(t, 3))) t random.uniform(0.01, 0.02) # 末端减速并做位置校正 while cur distance: step random.uniform(0.3, 1.2) * speed cur min(cur step, distance) track.append((round(cur, 1), round(t, 3))) t random.uniform(0.03, 0.06) return trackspeed控制整体拖得快还是慢0.8 是一个比较接近人工操作的经验值。返回的列表是 (x, timestamp) 的二元组实战中的上报数据通常还需要带上 y 轴坐标和压力值这里只做示意。时间戳的单位是秒易盾上报时一般要求毫秒级精度接入时注意换算。这条轨迹生成方法只用于联调环境验证自己的登录链路生产环境请直接使用用户的真实拖动数据。把轨迹生成写进生产代码企图绕过风控既不道德也不稳定而且易盾的模型会随攻击样本持续更新。4.2 缺口识别用像素差定位别急着上模板匹配DEMO 里如果需要自动找到背景图上的缺口位置最简单的做法是拿滑块图去背景图上做滑动对比找出差异最大的横坐标。用 PIL 的像素差就能实现from PIL import Image, ImageChops def find_gap(background_path, slider_path, step3): bg Image.open(background_path).convert(RGB) sl Image.open(slider_path).convert(RGB) bg_w, bg_h bg.size sl_w, sl_h sl.size best_x, best_diff 0, 0 for x in range(0, bg_w - sl_w, step): region bg.crop((x, 0, x sl_w, sl_h)) diff ImageChops.difference(region, sl) diff_val sum(diff.getdata()) if diff_val best_diff: best_diff diff_val best_x x return best_x这段代码先按步长 3 粗筛定位到差异最大的区域后可以再在附近做步长为 1 的细筛。步长越大速度越快但精度越低DEMO 场景下图片普遍在 300 像素左右粗筛加细筛的组合基本能在一秒内出结果。模板匹配的库用起来更省事但易盾的背景图经常带干扰纹理缺口附近的阴影和装饰物会让模板匹配误判。像素差方案虽然笨但对 DEMO 来说更直观出问题也好排查。真正生产级的缺口识别需要考虑光照变化和图片压缩噪声那已经是另一个量级的问题不建议在 DEMO 阶段投入。4.3 二次校验失败的排查顺序先看回调再看签名再看超时DEMO 里最让人头疼的不是写代码而是滑块明明拖过了登录却还是被拒。下面是这套链路上最常出现的几个情况现象优先排查点滑块拖完了但登录请求里没有 validate检查 success 回调是否真的执行隐藏字段有没有被赋值后端收到请求但校验接口返回参数错误确认 captchaId、token、validate 三个字段都传了且没有串位校验接口返回签名错误检查参数排序规则、空值过滤和 secretKey 是否与后台一致滑块通过、签名正确、接口也返回了但登录还是失败看后端有没有拿到 result 之后再走 login 流程还是直接忽略了返回值排查时建议在后端加一条请求日志分别记录“前端回调拿到 validate 的时间”和“后端调用易盾校验接口的响应时间”。如果两者间隔超过几秒多半是前端把 validate 塞隐藏字段的动作放在了异步回调里用户已经点了登录按钮但字段还没赋值。还有一个常见的低级错误后端写校验逻辑时只判断 HTTP 状态码是 200 就放行没有解析result字段的实际值。HTTP 200 只代表接口通了不代表校验通过。这个坑在 DEMO 阶段就要打好标记避免带病上线。5. 把 DEMO 抽成可替换的验证码接入层5.1 用适配器接口隔离开业务代码与验证码厂商DEMO 跑通之后下一步是把它从“能跑的页面”变成“能维护的模块”。常见的做法是抽象一个验证码适配器接口把易盾相关的逻辑全部封装在实现类里业务代码只依赖接口。这样将来换别家验证码或者同一个项目里不同环境用不同厂商都不需要动登录路由。class CaptchaAdapter: def verify(self, token: str, validate: str, **kwargs) - bool: raise NotImplementedError class YidunCaptchaAdapter(CaptchaAdapter): def __init__(self, secret_id: str, secret_key: str, captcha_id: str): self.secret_id secret_id self.secret_key secret_key self.captcha_id captcha_id def verify(self, token: str, validate: str, user_data: str ) - bool: params { secretId: self.secret_id, captchaId: self.captcha_id, token: token, validate: validate, timestamp: int(time.time() * 1000), data: user_data, } params[signature] make_sign(params, self.secret_key) resp requests.post(CHECK_URL, dataparams, timeout5).json() return resp.get(result) is True def get_captcha_adapter() - CaptchaAdapter: # 从配置中心读取厂商类型按需实例化 if config.captcha_provider yidun: return YidunCaptchaAdapter( secret_idconfig.yidun_secret_id, secret_keyconfig.yidun_secret_key, captcha_idconfig.yidun_captcha_id, ) raise ValueError(funknown captcha provider: {config.captcha_provider})接口的verify方法只暴露 token 和 validate 两个业务语义明确的参数调用方不需要知道签名怎么算、接口地址是什么。get_captcha_adapter这个工厂函数负责根据配置决定用哪家实现换厂商时新增一个类改一行配置。最后补一个自检脚本的验证技巧写两个测试用例一个用空字符串 validate 调用 verify 断言返回 False另一个把浏览器里手工滑出来的 token 和 validate 喂进去断言返回 True。每次改动签名逻辑或者参数组包规则后跑一遍这两个用例就能确认链路没有断。把密钥从代码里抽到环境变量的最早时间点就是在写这个适配器的时候。本文还有配套的精品资源点击获取
返回列表