python之路_生成验证码及user表继承

一、创建验证码

  在创建验证码的时候主要用到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

posted @ 2017-12-19 20:39  骑猪走秀  阅读(367)  评论(0编辑  收藏  举报