GeeTest行为验证
极验「行为验证」是一项可以帮助你的网站与APP识别与拦截机器程序批量自动化操作的SaaS应用。它是由极验开发的新一代人机验证产品,它不基于传统“问题-答案”的检测模式,而是通过利用深度学习对验证过程中产生的行为数据进行高维分析,发现人机行为模式与行为特征的差异,更加精准地区分人机行为。
使用场景
网站和APP,在所有可能被机器行为攻击的场景,例如但不限于注册、登录、短信接口、查询接口、营销活动、发帖评论等等,都可以部署使用「行为验证」,来抵御机器批量操作。
GeeTest in React
1、引入 gt.js
这一段代码可以直接在官网复制,我这边是有进行细微的语法调整。
/* eslint-disable radix */
/* initGeetest 1.0.0
* 用于加载id对应的验证码库,并支持宕机模式
* 暴露 initGeetest 进行验证码的初始化
* 一般不需要用户进行修改
*/
(function (global, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
// CommonJS
module.exports = global.document
? factory(global, true)
: function (w) {
if (!w.document) {
throw new Error('Geetest requires a window with a document');
}
return factory(w);
};
} else {
factory(global);
}
// eslint-disable-next-line func-names
})(typeof window !== 'undefined' ? window : this, function (window) {
if (typeof window === 'undefined') {
throw new Error('Geetest requires browser environment');
}
const { document } = window;
const { Math } = window;
const head = document.getElementsByTagName('head')[0];
function _Object(obj) {
this._obj = obj;
}
_Object.prototype = {
_each(process) {
const { _obj } = this;
for (const k in _obj) {
// eslint-disable-next-line no-prototype-builtins
if (_obj.hasOwnProperty(k)) {
process(k, _obj[k]);
}
}
return this;
},
};
function Config(config) {
const self = this;
new _Object(config)._each(function (key, value) {
self[key] = value;
});
}
Config.prototype = {
api_server: 'api.geetest.com',
protocol: 'http://',
type_path: '/gettype.php',
fallback_config: {
slide: {
static_servers: ['static.geetest.com', 'dn-staticdown.qbox.me'],
type: 'slide',
slide: '/static/js/geetest.0.0.0.js',
},
fullpage: {
static_servers: ['static.geetest.com', 'dn-staticdown.qbox.me'],
type: 'fullpage',
fullpage: '/static/js/fullpage.0.0.0.js',
},
},
_get_fallback_config() {
const self = this;
if (isString(self.type)) {
return self.fallback_config[self.type];
} else if (self.new_captcha) {
return self.fallback_config.fullpage;
} else {
return self.fallback_config.slide;
}
},
_extend(obj) {
const self = this;
new _Object(obj)._each(function (key, value) {
self[key] = value;
});
},
};
const isNumber = function (value) {
return typeof value === 'number';
};
const isString = function (value) {
return typeof value === 'string';
};
const isBoolean = function (value) {
return typeof value === 'boolean';
};
const isObject = function (value) {
return typeof value === 'object' && value !== null;
};
const isFunction = function (value) {
return typeof value === 'function';
};
const callbacks = {};
const status = {};
// eslint-disable-next-line func-names
const random = function () {
return parseInt(Math.random() * 10000) + new Date().valueOf();
};
// eslint-disable-next-line func-names
const loadScript = function (url, cb) {
const script = document.createElement('script');
script.charset = 'UTF-8';
script.async = true;
script.onerror = function () {
cb(true);
};
let loaded = false;
// eslint-disable-next-line func-names
// eslint-disable-next-line no-multi-assign
script.onload = script.onreadystatechange = function () {
if (
!loaded &&
(!script.readyState || script.readyState === 'loaded' || script.readyState === 'complete')
) {
loaded = true;
// eslint-disable-next-line func-names
setTimeout(function () {
cb(false);
}, 0);
}
};
script.src = url;
head.appendChild(script);
};
const normalizeDomain = function (domain) {
return domain.replace(/^https?:\/\/|\/$/g, '');
};
// eslint-disable-next-line func-names
const normalizePath = function (path) {
// eslint-disable-next-line no-param-reassign
path = path.replace(/\/+/g, '/');
if (path.indexOf('/') !== 0) {
// eslint-disable-next-line no-param-reassign
path = `/${path}`;
}
return path;
};
const normalizeQuery = function (query) {
if (!query) {
return '';
}
let q = '?';
new _Object(query)._each(function (key, value) {
if (isString(value) || isNumber(value) || isBoolean(value)) {
q = `${q + encodeURIComponent(key)}=${encodeURIComponent(value)}&`;
}
});
if (q === '?') {
q = '';
}
return q.replace(/&$/, '');
};
// eslint-disable-next-line func-names
const makeURL = function (protocol, domain, path, query) {
// eslint-disable-next-line no-param-reassign
domain = normalizeDomain(domain);
let url = normalizePath(path) + normalizeQuery(query);
if (domain) {
url = protocol + domain + url;
}
return url;
};
const load = function (protocol, domains, path, query, cb) {
// eslint-disable-next-line func-names
const tryRequest = function (at) {
const url = makeURL(protocol, domains[at], path, query);
// eslint-disable-next-line func-names
loadScript(url, function (err) {
if (err) {
if (at >= domains.length - 1) {
cb(true);
} else {
tryRequest(at + 1);
}
} else {
cb(false);
}
});
};
tryRequest(0);
};
// eslint-disable-next-line func-names
const jsonp = function (domains, path, config, callback) {
if (isObject(config.getLib)) {
config._extend(config.getLib);
callback(config);
return;
}
if (config.offline) {
callback(config._get_fallback_config());
return;
}
const cb = `geetest_${random()}`;
// eslint-disable-next-line no-param-reassign
// eslint-disable-next-line func-names
// eslint-disable-next-line no-param-reassign
window[cb] = function (data) {
if (data.status === 'success') {
callback(data.data);
} else if (!data.status) {
callback(data);
} else {
callback(config._get_fallback_config());
}
// eslint-disable-next-line no-param-reassign
window[cb] = undefined;
try {
// eslint-disable-next-line no-param-reassign
delete window[cb];
// eslint-disable-next-line no-empty
} catch (e) {}
};
load(
config.protocol,
domains,
path,
{
gt: config.gt,
callback: cb,
},
// eslint-disable-next-line func-names
function (err) {
if (err) {
callback(config._get_fallback_config());
}
}
);
};
const throwError = function (errorType, config) {
const errors = {
networkError: '网络错误',
};
if (typeof config.onError === 'function') {
config.onError(errors[errorType]);
} else {
throw new Error(errors[errorType]);
}
};
const detect = function () {
return !!window.Geetest;
};
if (detect()) {
status.slide = 'loaded';
}
const initGeetest = function (userConfig, callback) {
const config = new Config(userConfig);
if (userConfig.https) {
config.protocol = 'https://';
} else if (!userConfig.protocol) {
config.protocol = `${window.location.protocol}//`;
}
jsonp([config.api_server || config.apiserver], config.type_path, config, function (newConfig) {
const { type } = newConfig;
const init = function () {
config._extend(newConfig);
callback(new window.Geetest(config));
};
callbacks[type] = callbacks[type] || [];
const s = status[type] || 'init';
if (s === 'init') {
status[type] = 'loading';
callbacks[type].push(init);
load(
config.protocol,
newConfig.static_servers || newConfig.domains,
newConfig[type] || newConfig.path,
null,
function (err) {
if (err) {
status[type] = 'fail';
throwError('networkError', config);
} else {
status[type] = 'loaded';
const cbs = callbacks[type];
for (let i = 0, len = cbs.length; i < len; i += 1) {
const cb = cbs[i];
if (isFunction(cb)) {
cb();
}
}
callbacks[type] = [];
}
}
);
} else if (s === 'loaded') {
init();
} else if (s === 'fail') {
throwError('networkError', config);
} else if (s === 'loading') {
callbacks[type].push(init);
}
});
};
// eslint-disable-next-line no-param-reassign
window.initGeetest = initGeetest;
return initGeetest;
});
2、封装initGeeTestWithAjax 、initGeeTestWithAxios通用utils
export function initGeeTestWithAjax(url, captchaKeyName, otherConfigParams, initGeeTestCallback) {
$.ajax({
url: `${API_HOST}${url}`, // 加随机数防止缓存
type: 'get',
dataType: 'json',
success: (data) => {
$(captchaKeyName).val(data.captchaKey); // 设置验证码key
// 调用 initGeetest 初始化参数
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它调用相应的接口
initGeetest(
{
gt: data.geeTestId,
challenge: data.challenge,
new_captcha: true, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: 'float', // 产品形式,包括:float,popup
width: '100%',
...otherConfigParams,
},
(captchaObj) => {
initGeeTestCallback(captchaObj, data.captchaKey);
}
);
},
});
}
export function initGeeTestWithAxios(url, captchaKeyName, otherConfigParams, initGeeTestCallback) {
axios({
url: `${API_HOST}${url}`, // 加随机数防止缓存
method: 'GET',
}).then((data) => {
if (data) {
$(captchaKeyName).val(data.captchaKey); // 设置验证码key
// 调用 initGeetest 初始化参数
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它调用相应的接口
initGeetest(
{
gt: data.geeTestId,
challenge: data.challenge,
new_captcha: true, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: 'popup', // 产品形式,包括:float,popup
width: '100%',
...otherConfigParams,
},
(captchaObj) => {
initGeeTestCallback(captchaObj, data.captchaKey);
}
);
}
});
}
3、实际应用
const GtDemo = () => {
/**
* 其他配置参数: 更多配置参数请参考文档: https://docs.geetest.com/sensebot/apirefer/api/web#onSuccess-callback
*/
const otherConfigParams = {
lang: 'en', // 设置验证界面文字的语言 ;类型: 字符串 默认值: zh-cn
product: 'float', // 设置下一步验证的展现形式 类型: 字符串 默认: popup
width: '100px', // 设置按钮的长度 类型: 字符串 默认值: 300px
};
React.useEffect(() => {
initGeeTestWithAxios(
'/iam-16304/public/register',
'#captchaKey',
otherConfigParams,
initGeeTestCallback
);
}, []);
const initGeeTestCallback = (captchaObj, captchaKey) => {
$('#submit').click((e) => {
const result = captchaObj.getValidate();
if (!result) {
$('#notice').show();
setTimeout(() => {
$('#notice').hide();
}, 2000);
e.preventDefault();
}
});
captchaObj.appendTo('#captcha-box');
captchaObj.onReady(() => {
$('#wait').hide();
});
/**
* 校验成功后进行二次校验
*/
captchaObj.onSuccess(() => {
const result = captchaObj.getValidate();
if (result) {
$.ajax({
url: `${API_HOST}/iam-16304/public/validate`, // 加随机数防止缓存
type: 'POST',
dataType: 'json',
data: {
geetest_challenge: result.geetest_challenge,
geetest_validate: result.geetest_validate,
geetest_seccode: result.geetest_seccode,
captchaKey,
},
success: (data) => {
// 根据服务端二次验证的结果进行跳转等操作
alert('验证成功,后续为客制化操作');
console.log('验证成功', data);
},
});
}
});
};
return (
<>
<Header title="极验校验" />
<Content>
<div style={{ width: 300 }}>
<h1 style={{ textAlign: 'center' }}>极验验证Demo</h1>
<Form style={{ width: 300 }} labelAlign="left">
<TextField label="用户名" />
<Password label="密码" />
</Form>
<div style={{ width: 300 }}>
<input type="hidden" id="captchaKey" />
<br />
<div id="captcha-box">
<p id="wait">正在加载验证码......</p>
</div>
<br />
<p id="notice" style={{ display: 'none' }}>
请先完成验证
</p>
<Button style={{ float: 'right' }} color="primary" id="submit">
提交
</Button>
</div>
</div>
</Content>
</>
);
};
export default GtDemo;