add-GeetTest

view
王宇洋 3 years ago
parent 12a50abde0
commit e6ece1ff58

@ -26,6 +26,7 @@ login_manager = LoginManager()
login_manager.login_view = 'auth.login'
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])

File diff suppressed because one or more lines are too long

@ -5,9 +5,9 @@
{% block head %}
{{ super() }}
{{ super() }}
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
@ -15,20 +15,23 @@
<!-- Custom styles for this template -->
<link href="{{ url_for('static',filename='signin.css') }}" rel="stylesheet">
<link href="{{ url_for('static',filename='bg.css') }}" rel="stylesheet">
<script type="application/javascript" src="{{ url_for('static',filename='js/gt.js') }}"></script>
{% endblock %}
{% block body %}
<div class="onepage">
<div class = "onepage-bg" id = "onepagebg">
<div class="onepage">
<div class="onepage-bg" id="onepagebg">
<nav calss="navbar navbar-inverse bg-light " role="navigation" style="background-color: rgba(245,245,245,0.7);">
<nav calss="navbar navbar-inverse bg-light " role="navigation"
style="background-color: rgba(245,245,245,0.7);">
<div class="container">
{# 头部#}
{# 头部#}
<div class="navbar-header">
<button type="button" class=" btn-warning navbar-toggle " data-toggle="collapse" data-target=".navbar-collapse">
<button type="button" class=" btn-warning navbar-toggle " data-toggle="collapse"
data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@ -37,20 +40,20 @@
<a href="/" class="navbar-brand">Student Exchange Forum of BJUT</a>
</div>
{# 尾部#}
{# 尾部#}
<div class="collapse navbar-collapse">
<div class="nav navbar-nav navbar-right">
<li>
<a class="btn ref-signup chose" href="/auth/register">
Register
</a>
</li>
<a class="btn ref-signup chose" href="/auth/register">
Register
</a>
</li>
</div>
<div class="nav navbar-nav navbar-right">
<li class="chose">
<a class="btn btn-warning disabled ref-signup chose" >
<a class="btn btn-warning disabled ref-signup chose">
Signin
</a>
</li>
@ -76,17 +79,28 @@
{% endfor %}
<form role="form" class="form-signin" method="post" style="margin: 120px auto 0px auto;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputName" class="sr-only">Email address</label>
<input type="text" id="inputName" class="form-control" placeholder="StudentID/OrganizationID" name="user" required autofocus>
<input type="text" id="inputName" class="form-control" placeholder="StudentID/OrganizationID"
name="user" required autofocus>
<p id="p1"></p>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" name="pwd" placeholder="Password" required autofocus>
<input type="password" id="inputPassword" class="form-control" name="pwd" placeholder="Password"
required autofocus>
<p id="p2" class="text-danger"></p>
<div>
<label>完成验证:</label>
<div id="captcha">
<p id="wait" class="show">正在加载验证码......</p>
</div>
</div>
<br>
<p id="notice" class="hide">请先完成验证</p>
<div>
<h4><small>
Forgot your password?
@ -100,13 +114,15 @@
</div>
<button class="btn btn-lg btn-warning btn-block chose" id="submit" disabled="disabled" type="submit">Sign in</button>
<button class="btn btn-lg btn-warning btn-block chose" id="submit" disabled="disabled"
type="submit">Sign in
</button>
</form>
</div>
</div>
</div>
</div>
<script>
<script>
var stuID = document.getElementById('inputName');
var info = document.getElementById('p1');
var pwd = document.getElementById('inputPassword');
@ -116,20 +132,55 @@
var reg = /[0-9]{1,4}/;
{#var reg = /^\d{18}$/;#}
// 不符合规则的话
if(!reg.test(stuid)){
if (!reg.test(stuid)) {
info.innerHTML = 'Please enter StudentID or OrganizationID';
info.className = 'text-danger';
document.getElementById("submit").disabled=true;
}
else{
document.getElementById("submit").disabled = true;
} else {
info.innerHTML = '';
info.className = 'text-success';
}
if(info.className == 'text-success'){
if (info.className == 'text-success') {
document.getElementById("submit").disabled = false;
}
}
</script>
}
var handler = function (captchaObj) {
$("#submit").click(function (e) {
var result = captchaObj.getValidate();
if (!result) {
$("#notice").show();
setTimeout(function () {
$("#notice").hide();
}, 2000);
e.preventDefault();
}
});
// 将验证码加到id为captcha的元素里同时会有三个input的值用于表单提交
captchaObj.appendTo("#captcha");
captchaObj.onReady(function () {
$("#wait").hide();
});
};
$.ajax({
url: "register_check?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
success: function (data) {
// 调用 initGeetest 初始化参数
// 参数1配置参数
// 参数2回调回调的第一个参数验证码对象之后可以使用它调用相应的接口
initGeetest({
gt: data.gt,
challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "float", // 产品形式包括floatpopup
width: "100%"
}, handler);
}
});
</script>
{% endblock %}

@ -22,6 +22,7 @@ class Config:
FLASKY_FOLLOWERS_PER_PAGE = 50
FLASKY_COMMENTS_PER_PAGE = 30
FLASKY_LIKER_PER_PAGE = 50
CACHE_TYPE = "simple"
@staticmethod
def init_app(app):

Binary file not shown.

@ -1,4 +1,10 @@
import json
import os
import threading
import time
import requests
from flask import Response
from flask_migrate import Migrate
from app import create_app, db
from app.models import User, Role, Students, Permission, Post, Comment, Like, Notification, Transaction, Activity
@ -7,7 +13,33 @@ from app.models import User, Role, Students, Permission, Post, Comment, Like, No
# please run this file
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
migrate = Migrate(app, db, render_as_batch=True)
from geetest_config import GEETEST_ID, GEETEST_KEY, REDIS_HOST, REDIS_PORT, CYCLE_TIME, BYPASS_URL, \
GEETEST_BYPASS_STATUS_KEY
from sdk.geetest_lib import GeetestLib
# 发送bypass请求获取bypass状态并进行缓存
def check_bypass_status():
while True:
response = ""
params = {"gt": GEETEST_ID}
try:
response = requests.get(url=BYPASS_URL, params=params)
except Exception as e:
print(e)
if response and response.status_code == 200:
print(response.content)
bypass_status_str = response.content.decode("utf-8")
bypass_status = json.loads(bypass_status_str).get("status")
geetest_dict[GEETEST_BYPASS_STATUS_KEY] = bypass_status
else:
bypass_status = "fail"
geetest_dict[GEETEST_BYPASS_STATUS_KEY] = bypass_status
print("bypass状态已经获取并存入redis当前状态为-{}".format(bypass_status))
time.sleep(CYCLE_TIME)
@app.shell_context_processor
@ -16,5 +48,45 @@ def make_shell_context():
Comment=Comment, Like=Like, Notification=Notification, Transaction=Transaction, Activity=Activity)
geetest_dict = {}
# 从缓存中取出当前缓存的bypass状态(success/fail)
def get_bypass_cache():
bypass_status_cache = geetest_dict[GEETEST_BYPASS_STATUS_KEY]
bypass_status = bypass_status_cache
return bypass_status
# 验证初始化接口GET请求
@app.route("/auth/register_check", methods=["GET"])
def first_register():
# 必传参数
# digestmod 此版本sdk可支持md5、sha256、hmac-sha256md5之外的算法需特殊配置的账号联系极验客服
# 自定义参数,可选择添加
# user_id 客户端用户的唯一标识确定用户的唯一性作用于提供进阶数据分析服务可在register和validate接口传入不传入也不影响验证服务的使用若担心用户信息风险可作预处理(如哈希处理)再提供到极验
# client_type 客户端类型web电脑上的浏览器h5手机上的浏览器包括移动应用内完全内置的web_viewnative通过原生sdk植入app应用的方式unknown未知
# ip_address 客户端请求sdk服务器的ip地址
bypass_status = get_bypass_cache()
gt_lib = GeetestLib(GEETEST_ID, GEETEST_KEY)
digestmod = "md5"
user_id = "test"
param_dict = {"digestmod": digestmod, "user_id": user_id, "client_type": "web", "ip_address": "127.0.0.1"}
if bypass_status == "success":
result = gt_lib.register(digestmod, param_dict)
else:
result = gt_lib.local_init()
# 注意,不要更改返回的结构和值类型
return Response(result.data, content_type='application/json;charset=UTF-8')
@app.route("/favicon.ico")
def favicon():
return app.send_static_file('favicon.ico')
thread = threading.Thread(target=check_bypass_status)
thread.start()
app.secret_key = GeetestLib.VERSION
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')

@ -0,0 +1,7 @@
GEETEST_ID = "63840b116cd26b015c6e82d58b10515b"
GEETEST_KEY = "ed498dbf04feeb3337b2dc28536e09d4"
REDIS_HOST = "127.0.0.1"
REDIS_PORT = "6379"
CYCLE_TIME = 10
GEETEST_BYPASS_STATUS_KEY = "gt_server_bypass_status"
BYPASS_URL = "http://bypass.geetest.com/v1/bypass_status.php"

@ -0,0 +1,150 @@
import string
import random
import json
import requests
import hmac
import hashlib
from .geetest_lib_result import GeetestLibResult
# sdk lib包核心逻辑。
class GeetestLib:
IS_DEBUG = True # 调试开关,是否输出调试日志
API_URL = "http://api.geetest.com"
REGISTER_URL = "/register.php"
VALIDATE_URL = "/validate.php"
JSON_FORMAT = "1"
NEW_CAPTCHA = True
HTTP_TIMEOUT_DEFAULT = 5 # 单位:秒
VERSION = "python-flask:3.1.1"
GEETEST_CHALLENGE = "geetest_challenge" # 极验二次验证表单传参字段 chllenge
GEETEST_VALIDATE = "geetest_validate" # 极验二次验证表单传参字段 validate
GEETEST_SECCODE = "geetest_seccode" # 极验二次验证表单传参字段 seccode
def __init__(self, geetest_id, geetest_key):
self.geetest_id = geetest_id
self.geetest_key = geetest_key
self.libResult = GeetestLibResult()
def gtlog(self, message):
if self.IS_DEBUG:
print("gtlog: " + message)
# 验证初始化
def register(self, digestmod, param_dict):
self.gtlog("register(): 开始验证初始化, digestmod={0}.".format(digestmod));
origin_challenge = self.request_register(param_dict)
self.build_register_result(origin_challenge, digestmod)
self.gtlog("register(): 验证初始化, lib包返回信息={0}.".format(self.libResult));
return self.libResult
def request_register(self, param_dict):
param_dict.update({"gt": self.geetest_id, "sdk": self.VERSION, "json_format": self.JSON_FORMAT})
register_url = self.API_URL + self.REGISTER_URL
self.gtlog("requestRegister(): 验证初始化, 向极验发送请求, url={0}, params={1}.".format(register_url, param_dict))
try:
res = requests.get(register_url, params=param_dict, timeout=self.HTTP_TIMEOUT_DEFAULT)
res_body = res.text if res.status_code == requests.codes.ok else ""
self.gtlog("requestRegister(): 验证初始化, 与极验网络交互正常, 返回码={0}, 返回body={1}.".format(res.status_code, res_body))
res_dict = json.loads(res_body)
origin_challenge = res_dict["challenge"]
except Exception as e:
self.gtlog("requestRegister(): 验证初始化, 请求异常,后续流程走宕机模式, " + repr(e))
origin_challenge = ""
return origin_challenge
def local_init(self):
self.build_register_result("", "")
self.gtlog("local_init(): bypass当前状态为fail后续流程将进入宕机模式, " + self.libResult.data)
return self.libResult
# 构建验证初始化返回数据
def build_register_result(self, origin_challenge, digestmod):
# origin_challenge为空或者值为0代表失败
if not origin_challenge or origin_challenge == "0":
# 本地随机生成32位字符串
challenge = "".join(random.sample('abcdefghijklmnopqrstuvwxyz0123456789', 32))
data = json.dumps(
{"success": 0, "gt": self.geetest_id, "challenge": challenge, "new_captcha": self.NEW_CAPTCHA})
self.libResult.set_all(0, data, "bypass当前状态为fail后续流程走宕机模式")
else:
if digestmod == "md5":
challenge = self.md5_encode(origin_challenge + self.geetest_key)
elif digestmod == "sha256":
challenge = self.sha256_endode(origin_challenge + self.geetest_key)
elif digestmod == "hmac-sha256":
challenge = self.hmac_sha256_endode(origin_challenge, self.geetest_key)
else:
challenge = self.md5_encode(origin_challenge + self.geetest_key)
data = json.dumps(
{"success": 1, "gt": self.geetest_id, "challenge": challenge, "new_captcha": self.NEW_CAPTCHA})
self.libResult.set_all(1, data, "")
# 正常流程下(即验证初始化成功),二次验证
def successValidate(self, challenge, validate, seccode, param_dict={}):
self.gtlog(
"successValidate(): 开始二次验证 正常模式, challenge={0}, validate={1}, seccode={2}.".format(challenge, validate,
seccode))
if not self.check_param(challenge, validate, seccode):
self.libResult.set_all(0, "", "正常模式本地校验参数challenge、validate、seccode不可为空")
else:
response_seccode = self.requestValidate(challenge, validate, seccode, param_dict)
if not response_seccode:
self.libResult.set_all(0, "", "请求极验validate接口失败")
elif response_seccode == "false":
self.libResult.set_all(0, "", "极验二次验证不通过")
else:
self.libResult.set_all(1, "", "")
self.gtlog("successValidate(): 二次验证 正常模式, lib包返回信息={0}.".format(self.libResult))
return self.libResult
# 异常流程下(即验证初始化失败,宕机模式),二次验证
# 注意:由于是宕机模式,初衷是保证验证业务不会中断正常业务,所以此处只作简单的参数校验,可自行设计逻辑。
def failValidate(self, challenge, validate, seccode):
self.gtlog(
"failValidate(): 开始二次验证 宕机模式, challenge={0}, validate={1}, seccode={2}.".format(challenge, validate,
seccode))
if not self.check_param(challenge, validate, seccode):
self.libResult.set_all(0, "", "宕机模式本地校验参数challenge、validate、seccode不可为空.")
else:
self.libResult.set_all(1, "", "")
self.gtlog("failValidate(): 二次验证 宕机模式, lib包返回信息={0}.".format(self.libResult))
return self.libResult
# 向极验发送二次验证的请求POST方式
def requestValidate(self, challenge, validate, seccode, param_dict):
param_dict.update(
{"seccode": seccode, "json_format": self.JSON_FORMAT, "challenge": challenge, "sdk": self.VERSION,
"captchaid": self.geetest_id})
validate_url = self.API_URL + self.VALIDATE_URL
self.gtlog("requestValidate(): 二次验证 正常模式, 向极验发送请求, url={0}, params={1}.".format(validate_url, param_dict))
try:
res = requests.post(validate_url, data=param_dict, timeout=self.HTTP_TIMEOUT_DEFAULT)
res_body = res.text if res.status_code == requests.codes.ok else ""
self.gtlog(
"requestValidate(): 二次验证 正常模式, 与极验网络交互正常, 返回码={0}, 返回body={1}.".format(res.status_code, res_body))
res_dict = json.loads(res_body)
seccode = res_dict["seccode"]
except Exception as e:
self.gtlog("requestValidate(): 二次验证 正常模式, 请求异常, " + repr(e))
seccode = ""
return seccode
# 校验二次验证的三个参数校验通过返回true校验失败返回false
def check_param(self, challenge, validate, seccode):
return not (
challenge is None or challenge.isspace() or validate is None or validate.isspace() or seccode is None or seccode.isspace())
def md5_encode(self, value):
md5 = hashlib.md5()
md5.update(value.encode("utf-8"))
return md5.hexdigest()
def sha256_endode(self, value):
sha256 = hashlib.sha256()
sha256.update(value.encode("utf-8"))
return sha256.hexdigest()
def hmac_sha256_endode(self, value, key):
return hmac.new(key.encode("utf-8"), value.encode("utf-8"), digestmod=hashlib.sha256).hexdigest()

@ -0,0 +1,15 @@
# sdk lib包的返回结果信息。
class GeetestLibResult:
def __init__(self):
self.status = 0 # 成功失败的标识码1表示成功0表示失败
self.data = '' # 返回数据json格式
self.msg = '' # 备注信息,如异常信息等
def set_all(self, status, data, msg):
self.status = status
self.data = data
self.msg = msg
def __str__(self):
return "GeetestLibResult{{status={0}, data={1}, msg={2}}}".format(self.status, self.data, self.msg)
Loading…
Cancel
Save