- https://www.cnblogs.com/lbzbky/articles/11852230.html
- geetest.py(照抄) # 官方提供的SDK
- captcha_verify(照抄).py # 利用SDK生成的校验函数,发post请求的时候调用,校验验证码是否通过
- captcha.py # 前端页面验证码组件初始化的时候,发送get请求到后端的响应View
- 返回json,前端存储起来,等发post登录请求的时候需用到
### utils/geetest.py
# !/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import random
import json
import requests
import time
from hashlib import md5
if sys.version_info >= (3,):
xrange = range
VERSION = "3.2.0"
class GeeTestLib(object):
FN_CHALLENGE = "geetest_challenge"
FN_VALIDATE = "geetest_validate"
FN_SECCODE = "geetest_seccode"
GT_STATUS_SESSION_KEY = "gt_server_status"
API_URL = "http://api.geetest.com"
REGISTER_HANDLER = "/register.php"
VALIDATE_HANDLER = "/validate.php"
def __init__(self, captcha_id, private_key):
self.private_key = private_key
self.captcha_id = captcha_id
self.sdk_version = VERSION
self._response_str = ""
def pre_process(self, user_id=None):
"""
验证初始化预处理.
"""
status, challenge = self._register(user_id)
self._response_str = self._make_response_format(status, challenge)
return status
def _register(self, user_id=None):
challenge = self._register_challenge(user_id)
if len(challenge) == 32:
challenge = self._md5_encode("".join([challenge, self.private_key]))
return 1, challenge
else:
return 0, self._make_fail_challenge()
def get_response_str(self):
return self._response_str
def _make_fail_challenge(self):
rnd1 = random.randint(0, 99)
rnd2 = random.randint(0, 99)
md5_str1 = self._md5_encode(str(rnd1))
md5_str2 = self._md5_encode(str(rnd2))
challenge = md5_str1 + md5_str2[0:2]
return challenge
def _make_response_format(self, success=1, challenge=None):
if not challenge:
challenge = self._make_fail_challenge()
string_format = json.dumps(
{'success': success, 'gt': self.captcha_id, 'challenge': challenge})
return string_format
def _register_challenge(self, user_id=None):
if user_id:
register_url = "{api_url}{handler}?gt={captcha_ID}&user_id={user_id}".format(
api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id, user_id=user_id)
else:
register_url = "{api_url}{handler}?gt={captcha_ID}".format(
api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id)
try:
response = requests.get(register_url, timeout=2)
if response.status_code == requests.codes.ok:
res_string = response.text
else:
res_string = ""
except:
res_string = ""
return res_string
def success_validate(self, challenge, validate, seccode, user_id=None, gt=None, data='', userinfo=''):
"""
正常模式的二次验证方式.向geetest server 请求验证结果.
"""
if not self._check_para(challenge, validate, seccode):
return 0
if not self._check_result(challenge, validate):
return 0
validate_url = "{api_url}{handler}".format(
api_url=self.API_URL, handler=self.VALIDATE_HANDLER)
query = {
"seccode": seccode,
"sdk": ''.join(["python_", self.sdk_version]),
"user_id": user_id,
"data": data,
"timestamp": time.time(),
"challenge": challenge,
"userinfo": userinfo,
"captchaid": gt
}
backinfo = self._post_values(validate_url, query)
if backinfo == self._md5_encode(seccode):
return 1
else:
return 0
def _post_values(self, apiserver, data):
response = requests.post(apiserver, data)
return response.text
def _check_result(self, origin, validate):
encodeStr = self._md5_encode(self.private_key + "geetest" + origin)
if validate == encodeStr:
return True
else:
return False
def failback_validate(self, challenge, validate, seccode):
"""
failback模式的二次验证方式.在本地对轨迹进行简单的判断返回验证结果.
"""
if not self._check_para(challenge, validate, seccode):
return 0
validate_str = validate.split('_')
encode_ans = validate_str[0]
encode_fbii = validate_str[1]
encode_igi = validate_str[2]
decode_ans = self._decode_response(challenge, encode_ans)
decode_fbii = self._decode_response(challenge, encode_fbii)
decode_igi = self._decode_response(challenge, encode_igi)
validate_result = self._validate_fail_image(
decode_ans, decode_fbii, decode_igi)
return validate_result
def _check_para(self, challenge, validate, seccode):
return (bool(challenge.strip()) and bool(validate.strip()) and bool(seccode.strip()))
def _validate_fail_image(self, ans, full_bg_index, img_grp_index):
thread = 3
full_bg_name = str(self._md5_encode(str(full_bg_index)))[0:10]
bg_name = str(self._md5_encode(str(img_grp_index)))[10:20]
answer_decode = ""
for i in range(0, 9):
if i % 2 == 0:
answer_decode += full_bg_name[i]
elif i % 2 == 1:
answer_decode += bg_name[i]
x_decode = answer_decode[4:]
x_int = int(x_decode, 16)
result = x_int % 200
if result < 40:
result = 40
if abs(ans - result) < thread:
return 1
else:
return 0
def _md5_encode(self, values):
if type(values) == str:
values = values.encode()
m = md5(values)
return m.hexdigest()
def _decode_rand_base(self, challenge):
str_base = challenge[32:]
i = 0
temp_array = []
for i in xrange(len(str_base)):
temp_char = str_base[i]
temp_ascii = ord(temp_char)
result = temp_ascii - 87 if temp_ascii > 57 else temp_ascii - 48
temp_array.append(result)
decode_res = temp_array[0] * 36 + temp_array[1]
return decode_res
def _decode_response(self, challenge, userresponse):
if len(userresponse) > 100:
return 0
shuzi = (1, 2, 5, 10, 50)
chongfu = set()
key = {}
count = 0
for i in challenge:
if i in chongfu:
continue
else:
value = shuzi[count % 5]
chongfu.add(i)
count += 1
key.update({i: value})
res = 0
for i in userresponse:
res += key.get(i, 0)
res = res - self._decode_rand_base(challenge)
return res
### utils/captcha_verify.py
from django.conf import settings
from api.utils.geetest import GeeTestLib
def verify(verify_data, uid=None, extend_params=None):
"""第三方滑动验证码校验.
选用第三方的验证组件, 根据参数进行校验
根据布尔值辨别是否校验通过
Parameters
----------
verify_data : dict
请求数据
uid: string, default: None
用户UID, 如果存在就免受滑动验证码的限制
extend_params : dict
预留的扩展参数
Returns
-------
True OR False
"""
captcha_config = settings.GEE_TEST
if captcha_config.get("verify_status"):
status = True
if uid in captcha_config.get("not_verify"):
return True
gt = GeeTestLib(captcha_config["gee_test_access_id"], captcha_config["gee_test_access_key"])
challenge = verify_data.get(gt.FN_CHALLENGE, '')
validate = verify_data.get(gt.FN_VALIDATE, '')
seccode = verify_data.get(gt.FN_SECCODE, '')
# status = request.session.get(gt.GT_STATUS_SESSION_KEY, 1)
# user_id = request.session.get("user_id")
if status:
result = gt.success_validate(challenge, validate, seccode, None)
else:
result = gt.failback_validate(challenge, validate, seccode)
return True if result else False
else:
return True
### views/captcha.py(前端页面加载完毕,首次发起请求,获取'滑块验证'所需要的json参数)[也可以把这边逻辑写到LoginView的get方法中(节省url)]
# 极验测试: url(r'^captcha/$', views.CaptchaView.as_view()),
import json
from rest_framework.views import APIView
from api.utils.geetest import GeeTestLib
from rest_framework.response import Response
from django.conf import settings
class CaptchaView(APIView):
def get(self, request):
gt = GeeTestLib(settings.GEE_TEST["gee_test_access_id"], settings.GEE_TEST["gee_test_access_key"])
gt.pre_process()
# 设置 geetest session, 用于是否启用滑动验证码向 geetest 发起远程验证, 如果取不到的话只是对本地轨迹进行校验
# self.request.session[gt.GT_STATUS_SESSION_KEY] = status
# request.session["user_id"] = user_id
response_str = gt.get_response_str()
response_str = json.loads(response_str)
return Response({"error_no": 0, "data": response_str})
......
GEE_TEST = {
"gee_test_access_id": "your_gee_test_access_id",
"gee_test_access_key": "your_gee_test_access_key",
"verify_status": True, # 是否启用滑动验证码验证组件(True表示启用)
"not_verify": [
"xxxxxxxxxxxxxxxxxx", # 不用验证的用户(存放着用户的uid)
],
}
- 测试URL: http://127.0.0.1:8000/captcha/
- 返回结果:
{
"error_no": 0,
"data": {
"success": 1,
"gt": "b8a8ea14f2b000253462b3a0590076ba",
"challenge": "8a9de1d5abb04734bee8d3ac0d98da9c",
"new_captcha": true
}
}
- post请求(简单演示,实际需求应该写到
LoginView
里面): 校验verify(request.data)
的返回值True
或者False
- 若判断结果是True,则继续后面的登录校验逻辑
- False: 立即触发异常,返回错误消息给前端,登录逻辑到此为止
from utils.jiyanDemo.first_method.captcha_verify import verify
......
# url(r'^login_jiyan_demo/$', views.LoginJiyanDemoView.as_view()),
class LoginJiyanDemoView(APIView):
def post(self,request):
# 校验前端传过来的校验码参数是否正确(实际需求中,正确就继续登录逻辑,不正确就立即返回错误消息)
is_valid = verify(request.data)
print(request.data) # {'geetest_challenge': '864f4b47db92c0bc17b1a4a77d840dda4j', 'geetest_seccode': 'f2ddba6aa4743d357c4aeee74c6f8a77|jordan', 'geetest_validate': 'f2ddba6aa4743d357c4aeee74c6f8a77'}
print('验证的结果为: ',is_valid) # 验证的结果为: True
return Response({'msg':'响应请求成功,结果为: {}'.format(is_valid)})
前端部分(使用vue来演示)
src/utils
目录下面保存gt.js
(官方提供的SDK)
// gt.js
"v0.4.8 Geetest Inc.";
(function (window) {
"use strict";
if (typeof window === 'undefined') {
throw new Error('Geetest requires browser environment');
}
var document = window.document;
var Math = window.Math;
var head = document.getElementsByTagName("head")[0];
function _Object(obj) {
this._obj = obj;
}
_Object.prototype = {
_each: function (process) {
var _obj = this._obj;
for (var k in _obj) {
if (_obj.hasOwnProperty(k)) {
process(k, _obj[k]);
}
}
return this;
}
};
function Config(config) {
var self = this;
new _Object(config)._each(function (key, value) {
self[key] = value;
});
}
Config.prototype = {
api_server: 'api.geetest.com',
protocol: 'http://',
typePath: '/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: function () {
var 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: function (obj) {
var self = this;
new _Object(obj)._each(function (key, value) {
self[key] = value;
})
}
};
var isNumber = function (value) {
return (typeof value === 'number');
};
var isString = function (value) {
return (typeof value === 'string');
};
var isBoolean = function (value) {
return (typeof value === 'boolean');
};
var isObject = function (value) {
return (typeof value === 'object' && value !== null);
};
var isFunction = function (value) {
return (typeof value === 'function');
};
var MOBILE = /Mobi/i.test(navigator.userAgent);
var pt = MOBILE ? 3 : 0;
var callbacks = {};
var status = {};
var nowDate = function () {
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var hours = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if (month >= 1 && month <= 9) {
month = '0' + month;
}
if (day >= 0 && day <= 9) {
day = '0' + day;
}
if (hours >= 0 && hours <= 9) {
hours = '0' + hours;
}
if (minutes >= 0 && minutes <= 9) {
minutes = '0' + minutes;
}
if (seconds >= 0 && seconds <= 9) {
seconds = '0' + seconds;
}
var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds;
return currentdate;
}
var random = function () {
return parseInt(Math.random() * 10000) + (new Date()).valueOf();
};
var loadScript = function (url, cb) {
var script = document.createElement("script");
script.charset = "UTF-8";
script.async = true;
// 对geetest的静态资源添加 crossOrigin
if ( /static\.geetest\.com/g.test(url)) {
script.crossOrigin = "anonymous";
}
script.onerror = function () {
cb(true);
};
var loaded = false;
script.onload = script.onreadystatechange = function () {
if (!loaded &&
(!script.readyState ||
"loaded" === script.readyState ||
"complete" === script.readyState)) {
loaded = true;
setTimeout(function () {
cb(false);
}, 0);
}
};
script.src = url;
head.appendChild(script);
};
var normalizeDomain = function (domain) {
// special domain: uems.sysu.edu.cn/jwxt/geetest/
// return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn
return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest
};
var normalizePath = function (path) {
path = path.replace(/\/+/g, '/');
if (path.indexOf('/') !== 0) {
path = '/' + path;
}
return path;
};
var normalizeQuery = function (query) {
if (!query) {
return '';
}
var 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(/&$/, '');
};
var makeURL = function (protocol, domain, path, query) {
domain = normalizeDomain(domain);
var url = normalizePath(path) + normalizeQuery(query);
if (domain) {
url = protocol + domain + url;
}
return url;
};
var load = function (config, send, protocol, domains, path, query, cb) {
var tryRequest = function (at) {
var url = makeURL(protocol, domains[at], path, query);
loadScript(url, function (err) {
if (err) {
if (at >= domains.length - 1) {
cb(true);
// report gettype error
if (send) {
config.error_code = 508;
var url = protocol + domains[at] + path;
reportError(config, url);
}
} else {
tryRequest(at + 1);
}
} else {
cb(false);
}
});
};
tryRequest(0);
};
var 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;
}
var cb = "geetest_" + random();
window[cb] = function (data) {
if (data.status == 'success') {
callback(data.data);
} else if (!data.status) {
callback(data);
} else {
callback(config._get_fallback_config());
}
window[cb] = undefined;
try {
delete window[cb];
} catch (e) {
}
};
load(config, true, config.protocol, domains, path, {
gt: config.gt,
callback: cb
}, function (err) {
if (err) {
callback(config._get_fallback_config());
}
});
};
var reportError = function (config, url) {
load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', {
time: nowDate(),
captcha_id: config.gt,
challenge: config.challenge,
pt: pt,
exception_url: url,
error_code: config.error_code
}, function (err) {})
}
var throwError = function (errorType, config) {
var errors = {
networkError: '网络错误',
gtTypeError: 'gt字段不是字符串类型'
};
if (typeof config.onError === 'function') {
config.onError(errors[errorType]);
} else {
throw new Error(errors[errorType]);
}
};
var detect = function () {
return window.Geetest || document.getElementById("gt_lib");
};
if (detect()) {
status.slide = "loaded";
}
window.initGeetest = function (userConfig, callback) {
var config = new Config(userConfig);
if (userConfig.https) {
config.protocol = 'https://';
} else if (!userConfig.protocol) {
config.protocol = window.location.protocol + '//';
}
// for KFC
if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' ||
userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') {
config.apiserver = 'yumchina.geetest.com/'; // for old js
config.api_server = 'yumchina.geetest.com';
}
if(userConfig.gt){
window.GeeGT = userConfig.gt
}
if(userConfig.challenge){
window.GeeChallenge = userConfig.challenge
}
if (isObject(userConfig.getType)) {
config._extend(userConfig.getType);
}
jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) {
var type = newConfig.type;
var init = function () {
config._extend(newConfig);
callback(new window.Geetest(config));
};
callbacks[type] = callbacks[type] || [];
var s = status[type] || 'init';
if (s === 'init') {
status[type] = 'loading';
callbacks[type].push(init);
load(config, true, 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';
var cbs = callbacks[type];
for (var i = 0, len = cbs.length; i < len; i = i + 1) {
var 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);
}
});
};
})(window);
......
// 导入极验js模块
import '@/utils/gt'
- 第一次的get请求获取校验码参数,保存起来: /captcha/
- 带着校验码参数,发第二次的post请求: /login_jiyan_demo/
- 在Login.vue组件中的 created 中写上 this.getGeetest(); ,让登录组件创建完成时就向服务端发送get请求得到初始化验证界面
- get请求成功以后,后端返回3个重要参数(用于二次验证,post请求的时候会用到)保存起来
- 调用SDK的initGeetest(配置项,function),
- 传入配置项
- 传入后端返回的三个校验参数(必须)
- 验证组件的其他配置项可以看官网,根据自身需求配置(非必须)
- 传入function
- 把(captchaObj.appendTo("#geetest")将验证组件放在页面中id="geetest"的标签中
- 例如结构中可以这么写: <div id="geetest"></div>)
- 存储 captchaObj.getValidate() 对象
- 例如: var result = captchaObj.getValidate();this.geetestObj = result
### login.index.vue
<template>
......
<!--校验组件的结构-->
<el-form-item>
<div id="geetest"></div>
</el-form-item>
......
<!--极验测试按钮,正常情况下,写在login登录按钮即可-->
<el-button :loading="loading" type="warning" style="width:100%;margin-bottom:30px;" @click.native.prevent="postJiyanDemoData">极验测试</el-button>
</template>
<script>
......
import request from '@/utils/request'
// 配置get请求和post请求路径(简写,就不放到vuex或者绑定$API了)
var reqGetVerify = ()=>request({
url:'/captcha/',
method:'get',
responseType:'json'
})
var reqPostVerify = (data)=>request({
url:'/login_jiyan_demo/',
method:'post',
responseType:'json',
data,
})
export default {
data() {
return {
......
geetestObj:{}, // 保存极验对象
jiyanSecondVerify:false // 滑块验证结果是否成功的标志
}
},
methods:{
......
// 极验demo之get请求
async getJiyanDemoData(){
let res = await reqGetVerify()
let data = res.data
initGeetest(
{
// 后端的返回值+文档配置项
gt: data.gt,
challenge: data.challenge,
offline: !data.success,
new_captcha: true,
width: "100%"
},
(captchaObj)=> {
// 把验证码组件加载到预留的html结构中
captchaObj.appendTo("#geetest");
// 存储后端的返回值
captchaObj.onSuccess(()=> {
var result = captchaObj.getValidate();
this.geetestObj = result;
this.jiyanSecondVerify = true // 表示滑块验证成功
});
}
);
},
// 极验demo之post请求
async postJiyanDemoData(){
if(!this.jiyanSecondVerify){ // 优先校验滑块验证是否通过
this.$message({
type:'warning',
message:'请完成验证!'
})
return
}
// 打包存储的数据
let data = {
geetest_challenge: this.geetestObj.geetest_challenge,
geetest_seccode: this.geetestObj.geetest_seccode,
geetest_validate: this.geetestObj.geetest_validate,
}
// 发post请求
let res = await reqPostVerify(data)
// {msg: "响应请求成功,结果为: True"}
console.log(res)
}
},
mounted(){ // 调用(这里也可以写在created())
this.getJiyanDemoData()
}
}