一、创建验证码
在创建验证码的时候主要用到PIL模块,主要用的方法如下:from PIL import Image,ImageDraw,ImageFont
1、创建验证码背景图
为了保证验证码每次的背景色不同,我们将上述color参数通过随机方式获取(颜色由三组在255之内的整数表示),获取颜色的函数如下:
import random def get_random_color(): return (random.randint(0,255),random.randint(0,255),random.randint(0,255))
2、添加验证码内容
验证码应该为区分大小写的字母和数字的随机字符组成,获取这样的随机字符的方式如下:
def get_random_char(): random_num=str(random.randint(0,9)) random_upper_alpha=chr(random.randint(65,90)) random_lower_alpha=chr(random.randint(97,122)) random_char=random.choice([random_num,random_upper_alpha,random_lower_alpha]) return random_char
3、给验证码加干扰元素
4、验证码图片保存
为了前端渲染请求验证码的时候方便,我们需要将验证码图片保存在内存中,需要用到io模块,具体引入方式为:from io import BytesIO,应用如下:
生成验证码图片的代码如下:
from PIL import Image,ImageDraw,ImageFont import random from io import BytesIO def get_valid_img(request): def get_random_color(): return (random.randint(0,255),random.randint(0,255),random.randint(0,255)) def get_random_char(): random_num=str(random.randint(0,9)) random_upper_alpha=chr(random.randint(65,90)) random_lower_alpha=chr(random.randint(97,122)) random_char=random.choice([random_num,random_upper_alpha,random_lower_alpha]) return random_char #生成验证码背景图片 width=200 height=35 image=Image.new(mode="RGB",size=(width,height),color=get_random_color()) #给验证码背景图片添加验证码内容 draw=ImageDraw.Draw(image,mode="RGB") font=ImageFont.truetype("blog/static/font/kumo.ttf",32) valid_code_str="" for i in range(1,6): char=get_random_char() valid_code_str+=char draw.text([i*30,3],char,get_random_color(),font=font) #给验证码加干扰 for i in range(80): draw.point((random.randint(0,width),random.randint(0,height)),get_random_color()) for i in range(5): x1=random.randint(0,width) y1=random.randint(0,height) x2=random.randint(0,width) y2=random.randint(0,height) draw.line((x1,y1,x2,y2),get_random_color()) #将生成的整个验证码图片保存到内存 f=BytesIO() image.save(f,"png") #从内存中将验证码图片取出来 data=f.getvalue() #将验证码内容存在session,用于登录验证使用 request.session["valid_code_str"]=valid_code_str print(valid_code_str) return HttpResponse(data)
5、前端获取验证码图片
前端img标签通过src请求生成验证码的函数,便可显示此验证码图片:
<img src="/get_valid_img/" width="210" height="35" id="valid_img">
如果验证码看不清,我们需要刷新验证码该如何实现呢?很简单,通过给这个img标签绑定点击事件,重新请求获取验证码图片即可,方式入如下:
$("#valid_img").click(function () { $(this)[0].src+="?" //点击图片后重新请求获取验证码函数 });
二、滑动验证码插件
geetset.py文件:
import sys import random import json import requests import time from hashlib import md5 if sys.version_info >= (3,): xrange = range VERSION = "3.0.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" JSON_FORMAT = False 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,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""): """ 验证初始化预处理. //TO DO arrage the parameter """ status, challenge = self._register(user_id,new_captcha,JSON_FORMAT,client_type,ip_address) self._response_str = self._make_response_format(status, challenge,new_captcha) return status def _register(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""): pri_responce = self._register_challenge(user_id,new_captcha,JSON_FORMAT,client_type,ip_address) if pri_responce: if JSON_FORMAT == 1: response_dic = json.loads(pri_responce) challenge = response_dic["challenge"] else: challenge = pri_responce else: challenge=" " 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,new_captcha=1): if not challenge: challenge = self._make_fail_challenge() if new_captcha: string_format = json.dumps( {'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":True}) else: string_format = json.dumps( {'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":False}) return string_format def _register_challenge(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""): if user_id: register_url = "{api_url}{handler}?gt={captcha_ID}&user_id={user_id}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format( api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id, user_id=user_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address) else: register_url = "{api_url}{handler}?gt={captcha_ID}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format( api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address) 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='',JSON_FORMAT=1): """ 正常模式的二次验证方式.向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, "json_format":JSON_FORMAT } backinfo = self._post_values(validate_url, query) if JSON_FORMAT == 1: backinfo = json.loads(backinfo) backinfo = backinfo["seccode"] 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_result = self._failback_check_result( challenge, validate,) return validate_result def _failback_check_result(self,challenge,validate): encodeStr = self._md5_encode(challenge) if validate == encodeStr: return True else: return False def _check_para(self, challenge, validate, seccode): return (bool(challenge.strip()) and bool(validate.strip()) and bool(seccode.strip())) def _md5_encode(self, values): if type(values) == str: values = values.encode() m = md5(values) return m.hexdigest()
view视图函数
from blog.geetest import GeetestLib pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c" pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4" def login2(request): return render(request,"login2.html") def pcgetcaptcha(request): user_id = 'test' gt = GeetestLib(pc_geetest_id, pc_geetest_key) status = gt.pre_process(user_id) request.session[gt.GT_STATUS_SESSION_KEY] = status request.session["user_id"] = user_id response_str = gt.get_response_str() return HttpResponse(response_str) def pcajax_validate(request): if request.method == "POST": gt = GeetestLib(pc_geetest_id, pc_geetest_key) challenge = request.POST.get(gt.FN_CHALLENGE, '') validate = request.POST.get(gt.FN_VALIDATE, '') seccode = request.POST.get(gt.FN_SECCODE, '') status = request.session[gt.GT_STATUS_SESSION_KEY] user_id = request.session["user_id"] username=request.POST.get("username") userpswd=request.POST.get("userpswd") loginResponse = {"user": None, "error": ""} if status: result = gt.success_validate(challenge, validate, seccode, user_id) else: result = gt.failback_validate(challenge, validate, seccode) print(result) if result: user=auth.authenticate(username=username,password=userpswd) if user: loginResponse["user"]=user.username auth.login(request,user) else: loginResponse["error"]="用户名或者密码错误" else: loginResponse["error"]="验证码校验错误" return JsonResponse(loginResponse) return HttpResponse("error")
login2.html文件:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <!-- 引入封装了failback的接口--initGeetest --> <script src="http://static.geetest.com/static/tools/gt.js"></script> </head> <body> <div class="container-fluid " style="background-color: #cccccc;height: 650px"> <div class="row"> <div class="col-sm-offset-11"> <a href="/register/">注册</a> <a href="/modify/">修改密码</a> </div> </div> <form class="form-horizontal" style="margin-top: 20px"> {% csrf_token %} <div class="col-sm-offset-4 col-sm-8"> <h2>滑动验证码登录页面</h2> </div> <div class="form-group"> <label for="username" class="col-sm-2 control-label col-sm-offset-2">用户名:</label> <div class="col-sm-4"> <input type="text" class="form-control" id="username" placeholder="Username"> </div> </div> <div class="form-group"> <label for="userpswd" class="col-sm-2 control-label col-sm-offset-2">密码:</label> <div class="col-sm-4"> <input type="password" class="form-control" id="userpswd" placeholder="Password"> <span class="error" id="login_error"></span> </div> </div> <div class="form-group"> <div class="col-sm-offset-4 col-sm-4"> <input type="button" class="btn btn-success btn-lg btn-block" value="登录" id="login"> </div> </div> <div id="popup-captcha"></div> </form> <div style="margin-left: 220px"> <img src="/static/3.jpg" alt="" class="img-rounded"> <img src="/static/3.jpg" alt="" class="img-circle"> <img src="/static/3.jpg" alt="" class="img-thumbnail"> </div> </div> <script src="/static/js/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> <script> var handlerPopup = function (captchaObj) { // 成功的回调 captchaObj.onSuccess(function () { var validate = captchaObj.getValidate(); $.ajax({ url: "/pc-geetest/ajax_validate", // 进行二次验证 type: "post", dataType: "json", data: { username: $("#username").val(), userpswd: $("#userpswd").val(), csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(), geetest_challenge: validate.geetest_challenge, geetest_validate: validate.geetest_validate, geetest_seccode: validate.geetest_seccode }, success: function (data) { if (data.user) { {# alert(location.href);#} {# alert(location.search);#} if (location.search.slice(6)) { location.href = location.search.slice(6) } else { location.href = "/index/" } } else { $("#login_error").text(data.error).css("color", "red"); //设置定时器,让错误信息过一段时间后消失 setTimeout(function () { $("#login_error").text() }, 3000) } } }); }); $("#login").click(function () { captchaObj.show(); }); // 将验证码加到id为captcha的元素里 captchaObj.appendTo("#popup-captcha"); // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html }; // 验证开始需要向网站主后台获取id,challenge,success(是否启用failback) $.ajax({ url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存 type: "get", dataType: "json", success: function (data) { // 使用initGeetest接口 // 参数1:配置参数 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件 initGeetest({ gt: data.gt, challenge: data.challenge, product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注 // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config }, handlerPopup); } }); </script> </body> </html>
三、登录验证
我们知道,django为我们提供了auth登录验证模块,会很方便的验证用户是否登陆成功,但是前提是用户名和密码信息是存在django自带的user表,也就是说auth模块是和user表绑定使用的。但是在项目中user表作为用户信息表显然字段不能满足我们的要求。我们的解决的方式当然是自己通过models重新创建用户信息表,但是我们可以通过让我自己创建的表继承user表,这样验证模块同样也就可以使用了。具体继承方式如下:
通过这样的方式继承user表,必须在setting文件中做如下配置,否则不能完成数据库迁移:
通过如上的继承以后,我们就可以正常的使用auth模块进行登录验证,如实例:
def login(request): if request.is_ajax(): username=request.POST.get("username") userpswd=request.POST.get("userpswd") valid_cod=request.POST.get("valid_code") print(valid_cod) valid_code_str=request.session.get("valid_code_str") print(valid_code_str) loginResponse={"user":None,"error":""} if valid_code_str.upper()==valid_cod.upper(): user=auth.authenticate(username=username,password=userpswd) if user: auth.login(request,user) loginResponse["user"]=user.username else: loginResponse["error"]="username or password error " else: loginResponse["error"]="valid code error" return JsonResponse(loginResponse) #可以将字典数据直接序列化,并发回前端,且前端不用反序列化 return render(request,"login.html")
四、JsonResponse
如上实例,我们通过如下方式导入JsonResponse后,我们就能直接将字典类型的数据发回给前端的ajax请求的回调函数,它的作用就是即帮我们做了数据的序列化,又做了数据的反序列化,即后端在发数据时不需要先序列化,前端的回调函数在接收到数据时也不用反序列化。引入方式为:from django.http import JsonResponse