parent
c2ecb1aa3e
commit
4b2a3c7508
File diff suppressed because one or more lines are too long
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 3.0.3 on 2021-03-24 17:51 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
|
||||||
|
dependencies = [ |
||||||
|
('api', '0023_auto_20210316_1602'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='userinfo', |
||||||
|
name='download_times', |
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='可用下载次数,需要用户充值'), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,149 @@ |
|||||||
|
import random |
||||||
|
import json |
||||||
|
import requests |
||||||
|
import hmac |
||||||
|
import hashlib |
||||||
|
|
||||||
|
from .geetest_lib_result import GeetestLibResult |
||||||
|
|
||||||
|
|
||||||
|
# sdk lib包,核心逻辑。 |
||||||
|
class GeetestLib: |
||||||
|
IS_DEBUG = False # 调试开关,是否输出调试日志 |
||||||
|
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) |
@ -0,0 +1,82 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# -*- coding:utf-8 -*- |
||||||
|
# project: 3月 |
||||||
|
# author: NinEveN |
||||||
|
# date: 2021/3/25 |
||||||
|
|
||||||
|
import requests |
||||||
|
import json |
||||||
|
import logging |
||||||
|
from django.core.cache import cache as redis_connect |
||||||
|
from fir_ser.settings import GEETEST_ID, GEETEST_KEY, GEETEST_BYPASS_URL, \ |
||||||
|
GEETEST_BYPASS_STATUS_KEY |
||||||
|
from api.utils.geetest.geetest_lib import GeetestLib |
||||||
|
|
||||||
|
logger = logging.getLogger(__file__) |
||||||
|
|
||||||
|
|
||||||
|
# 发送bypass请求,获取bypass状态并进行缓存(如何缓存可根据自身情况合理选择,这里是使用redis进行缓存) |
||||||
|
def check_bypass_status(): |
||||||
|
response = "" |
||||||
|
params = {"gt": GEETEST_ID} |
||||||
|
try: |
||||||
|
response = requests.get(url=GEETEST_BYPASS_URL, params=params) |
||||||
|
except Exception as e: |
||||||
|
logger.error("check_bypass_status failed Exception:%s" % e) |
||||||
|
if response and response.status_code == 200: |
||||||
|
logger.debug("check_bypass_status success %s" % response.content) |
||||||
|
bypass_status_str = response.content.decode("utf-8") |
||||||
|
bypass_status = json.loads(bypass_status_str).get("status") |
||||||
|
redis_connect.set(GEETEST_BYPASS_STATUS_KEY, bypass_status) |
||||||
|
else: |
||||||
|
bypass_status = "fail" |
||||||
|
redis_connect.set(GEETEST_BYPASS_STATUS_KEY, bypass_status) |
||||||
|
logger.debug("bypass状态已经获取并存入redis,当前状态为 {}".format(bypass_status)) |
||||||
|
|
||||||
|
|
||||||
|
# 从缓存中取出当前缓存的bypass状态(success/fail) |
||||||
|
def get_bypass_cache(): |
||||||
|
bypass_status_cache = redis_connect.get(GEETEST_BYPASS_STATUS_KEY) |
||||||
|
if not bypass_status_cache: |
||||||
|
bypass_status_cache = 'fail' |
||||||
|
return bypass_status_cache |
||||||
|
|
||||||
|
|
||||||
|
# 验证初始化接口,GET请求 |
||||||
|
def first_register(user_id, ip_address): |
||||||
|
# 必传参数 |
||||||
|
# digestmod 此版本sdk可支持md5、sha256、hmac-sha256,md5之外的算法需特殊配置的账号,联系极验客服 |
||||||
|
# 自定义参数,可选择添加 |
||||||
|
# user_id 客户端用户的唯一标识,确定用户的唯一性;作用于提供进阶数据分析服务,可在register和validate接口传入,不传入也不影响验证服务的使用;若担心用户信息风险,可作预处理(如哈希处理)再提供到极验 |
||||||
|
# client_type 客户端类型,web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生sdk植入app应用的方式;unknown:未知 |
||||||
|
# ip_address 客户端请求sdk服务器的ip地址 |
||||||
|
bypass_status = get_bypass_cache() |
||||||
|
gt_lib = GeetestLib(GEETEST_ID, GEETEST_KEY) |
||||||
|
digestmod = "md5" |
||||||
|
param_dict = {"digestmod": digestmod, "user_id": user_id, "client_type": "web", "ip_address": ip_address} |
||||||
|
if bypass_status == "success": |
||||||
|
result = gt_lib.register(digestmod, param_dict) |
||||||
|
else: |
||||||
|
result = gt_lib.local_init() |
||||||
|
# 注意,不要更改返回的结构和值类型 |
||||||
|
return json.loads(result.data) |
||||||
|
|
||||||
|
|
||||||
|
# 二次验证接口,POST请求 |
||||||
|
def second_validate(rdata): |
||||||
|
challenge = rdata.get(GeetestLib.GEETEST_CHALLENGE, None) |
||||||
|
validate = rdata.get(GeetestLib.GEETEST_VALIDATE, None) |
||||||
|
seccode = rdata.get(GeetestLib.GEETEST_SECCODE, None) |
||||||
|
bypass_status = get_bypass_cache() |
||||||
|
gt_lib = GeetestLib(GEETEST_ID, GEETEST_KEY) |
||||||
|
if bypass_status == "success": |
||||||
|
result = gt_lib.successValidate(challenge, validate, seccode) |
||||||
|
else: |
||||||
|
result = gt_lib.failValidate(challenge, validate, seccode) |
||||||
|
# 注意,不要更改返回的结构和值类型 |
||||||
|
if result.status == 1: |
||||||
|
response = {"result": "success", "version": GeetestLib.VERSION} |
||||||
|
else: |
||||||
|
response = {"result": "fail", "version": GeetestLib.VERSION, "msg": result.msg} |
||||||
|
logger.info("geetest second_validate info %s" % response) |
||||||
|
return response |
@ -0,0 +1,70 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# -*- coding:utf-8 -*- |
||||||
|
# project: 3月 |
||||||
|
# author: NinEveN |
||||||
|
# date: 2021/3/25 |
||||||
|
|
||||||
|
from rest_framework.throttling import SimpleRateThrottle |
||||||
|
import hashlib |
||||||
|
|
||||||
|
from rest_framework.exceptions import Throttled |
||||||
|
|
||||||
|
|
||||||
|
class VisitShortThrottle(SimpleRateThrottle): |
||||||
|
"""短连接用户访问频率限制1""" |
||||||
|
scope = "ShortAccessUser1" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'short_access_' + self.get_ident(request) + hashlib.md5( |
||||||
|
request.META.get('HTTP_USER_AGENT', '').encode("utf-8")).hexdigest() |
||||||
|
|
||||||
|
|
||||||
|
class InstallShortThrottle(SimpleRateThrottle): |
||||||
|
"""短连接用户访问频率限制2""" |
||||||
|
scope = "ShortAccessUser2" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'short_access_' + self.get_ident(request) |
||||||
|
|
||||||
|
|
||||||
|
class LoginUserThrottle(SimpleRateThrottle): |
||||||
|
"""登录用户访问频率限制""" |
||||||
|
scope = "LoginUser" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
if hasattr(request.user, 'uid'): |
||||||
|
return request.user.uid |
||||||
|
else: |
||||||
|
self.get_ident(request) |
||||||
|
|
||||||
|
|
||||||
|
class VisitRegister1Throttle(SimpleRateThrottle): |
||||||
|
"""注册或者登陆限速1""" |
||||||
|
scope = "RegisterUser1" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'login_register_' + self.get_ident(request) |
||||||
|
|
||||||
|
|
||||||
|
class VisitRegister2Throttle(SimpleRateThrottle): |
||||||
|
"""注册或者登陆限速2""" |
||||||
|
scope = "RegisterUser2" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'login_register_' + self.get_ident(request) |
||||||
|
|
||||||
|
|
||||||
|
class GetAuthC1Throttle(SimpleRateThrottle): |
||||||
|
"""注册或者登陆限速1""" |
||||||
|
scope = "GetAuthC1" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'get_auth_' + self.get_ident(request) |
||||||
|
|
||||||
|
|
||||||
|
class GetAuthC2Throttle(SimpleRateThrottle): |
||||||
|
"""注册或者登陆限速2""" |
||||||
|
scope = "GetAuthC2" |
||||||
|
|
||||||
|
def get_cache_key(self, request, view): |
||||||
|
return 'get_auth_' + self.get_ident(request) |
Loading…
Reference in new issue