Django实现验证码

作者: 若与 | 来源:发表于2016-12-10 11:55 被阅读1296次

    Django实现验证码

    背景知识

    1. 验证码的作用

    • 防恶意破解密码:防止,使用程序或机器人恶意去试密码.为了提高用户的体验,用户输入错误以后,才会要求输入验证码.
    • 防论坛灌水:这个是很常见的。有一种程序叫做顶帖机,如果无限制的刷,整个论坛可能到处是拉圾信息,比如,百度贴吧 ,你只要是新用户或者刚刚关注的贴吧,要是发帖,会马上出现验证码。
    • 有效防止注册,以防,使用程序或机器人去无限制注册账号.
    • 防刷票,网上有很多投票类的网站.

    2. 验证码的原理

    验证码于服务器端生成,发送给客户端,并以图像格式显示。客户端提交所显示的验证码,客户端接收并进行比较,若比对失败则不能实现登录或注册,反之成功后跳转相应界面。

    验证码原理与流程

    代码实现

    废话不多说,先上代码:
    # encoding:utf-8
    from PIL import Image, ImageDraw, ImageFont
    import random, StringIO
    import os
    from math import ceil
    import base64

    current_path = os.path.normpath(os.path.dirname(__file__))
    
    
    class Captcha(object):
    # 定义一个验证码类,
    def __init__(self, request):
        self.django_request = request
        self.session_key = request.session.session_key
        self.words = []
    
        # image size (pix)
        self.img_width = 150
        self.img_height = 30
    
        # default type
        self.type = 'number'
    
    def _get_font_size(self):
        """  将图片高度的80%作为字体大小
        """
        s1 = int(self.img_height * 0.8)
        s2 = int(self.img_width / len(self.code))
        return int(min((s1, s2)) + max((s1, s2)) * 0.05)
    
    def _get_words(self):
        """ The words list
        """
        # 扩充单词列表
        if self.words:
            return set(self.words)
    
        file_path = os.path.join(current_path, 'words.list')
        f = open(file_path, 'r')
        return set([line.replace('\n', '') for line in f.readlines()])
    
    def _set_answer(self, answer):
        """  设置答案
        """
        self.django_request.session[self.session_key] = str(answer)
    
    def _yield_code(self):
        """  生成验证码数字,以及答案
        """
       # 数字公式验证码
        def number():
            m, n = 1, 50
            x = random.randrange(m, n)
            y = random.randrange(m, n)
    
            r = random.randrange(0, 2)
            if r == 0:
                code = "%s - %s = ?" % (x, y)
                z = x - y
            else:
                code = "%s + %s = ?" % (x, y)
                z = x + y
            self._set_answer(z)
            return code
    
        fun = eval(self.type.lower())
        return fun()
    
    def display(self):
        """  把生成的验证码图片改成数据流返回
        """
    
        # 字体颜色
        self.font_color = ['black', 'darkblue', 'darkred']
    
        # 背景颜色,随机生成
        self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255))
    
        # 字体
        self.font_path = os.path.join(current_path, 'timesbi.ttf')
        # self.font_path = os.path.join(current_path,'Menlo.ttc')
    
        # 生成的验证码只做一次验证,就会清空
        self.django_request.session[self.session_key] = ''
    
        # 使用 PIL创建画布
        im = Image.new('RGB', (self.img_width, self.img_height), self.background)
        
        # 生成验证码
        self.code = self._yield_code()
    
        # 设置字体大小
        self.font_size = self._get_font_size()
    
        # 实例化一个绘图
        draw = ImageDraw.Draw(im)
    
        # 在画布绘图,写验证码
        if self.type == 'word':
            c = int(8 / len(self.code) * 3) or 3
        elif self.type == 'number':
            c = 4
    
        for i in range(random.randrange(c - 2, c)):
            line_color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255))
            xy = (
                random.randrange(0, int(self.img_width * 0.2)),
                random.randrange(0, self.img_height),
                random.randrange(3 * self.img_width / 4, self.img_width),
                random.randrange(0, self.img_height)
            )
            draw.line(xy, fill=line_color, width=int(self.font_size * 0.1))
            # draw.arc(xy,fill=line_color,width=int(self.font_size*0.1))
        # draw.arc(xy,0,1400,fill=line_color)
        # code part
        j = int(self.font_size * 0.3)
        k = int(self.font_size * 0.5)
        x = random.randrange(j, k)  # starts point
        for i in self.code:
            # 上下抖动量,字数越多,上下抖动越大
            m = int(len(self.code))
            y = random.randrange(1, 3)
            if i in ('+', '=', '?'):
                # 对计算符号等特殊字符放大处理
                m = ceil(self.font_size * 0.8)
            else:
                # 字体大小变化量,字数越少,字体大小变化越多
                m = random.randrange(0, int(45 / self.font_size) + int(self.font_size / 5))
            self.font = ImageFont.truetype(self.font_path.replace('\\', '/'), self.font_size + int(ceil(m)))
            draw.text((x, y), i, font=self.font, fill=random.choice(self.font_color))
            x += self.font_size * 0.9
        del x
        del draw
        # 序列化处理
        buf = StringIO.StringIO()
        im.save(buf, 'gif')
        buf.closed
        data = base64.encodestring(buf.getvalue())
        return data
    
    def validate(self, code):
        """
        检查用户输入和服务器上的密码是否一致
        """
        if not code:
            return False
        _code = self.django_request.session.get(self.session_key) or ''
        self.django_request.session[self.session_key] = ''
        return _code.lower() == str(code).lower()
    
    def check(self, code):
        """
        检查用户输入和服务器上保存的密码是否一致
        """
        return self.validate(code)
    

    上面使用的库如下:

        from PIL import Image, ImageDraw, ImageFont
        import random, StringIO
        import os
        from math import ceil
        import base64
    

    说明:

    • PIL 画图,生成图片
    • random 随机生成数 math用于计算
    • StringIO将图片格式转成数据流用于网络传输
    • base64,用户编码,数据传输,应前端要求处理跨域API的问题

    需要强调的是:
    我把用户的验证码的答案保存在用户的session中,保存在服务器上

    self.django_request.session[self.session_key] = str(answer)
    

    每一个用户访问都是会实例化一个request.seesion对象,所以,用户区分开了.

    self.session_key = request.session.session_key
    

    同一用户在不同地方同时登录,对应的request.session.session_key不同,所以也区分了异地同时登录,出现混乱的情况.

    django的view视图

    from common.CaptchaVerify import Captcha
    
    def captchaCode(request):
        ca = Captcha(request)
        ca.type = 'number'
        raw = ca.display()
        response = JsonResponse(raw)
        return response
    
    def login(request):
        _code = request.POST.get('code') or ''
        if not _code:
            data = {'code': 1, 'messeage': 'verify fail, captchacode error'}
            response = ReturnJson(data, status=401).get()
            return response
        ca = Captcha(request)
        if not ca.check(_code):
            data = {'code': 1, 'messsage': 'verify fail, captchacode error'}
            response = ReturnJson(data, status=401).get()
            return response
    

    这里,用户可以直接调用,那个类,如果需要定制,可以自己在类中修改,符合自己的业务需求.

    ps:
    验证码绘制规则
    • 均匀绘画字符,居中
    • 字符颜色要比较深
    • 要有线条雪花等干扰元素
    • 一切能随机的都随机
    • 考虑到用户的体验,老是错误,开始降低难度(哈哈哈!!)

    *下期预告

    相关文章

      网友评论

      • sunlin1234:你使用过那个第三方的库么
        simple-captcha这个第三方的验证码么,哪里面的背景的图片是怎么做的
        喇喇_a804:您好,前端收到数据流能转换为图形验证码不?
        若与: @sunlin1234 我看了一下,你说的那个,原理都一样的,simple-captcha做成一个app级别。你找到view.py文件。里面很详细。有什么不懂的,可以再问我。
        若与: @sunlin1234 ,我看一下,然后告诉你

      本文标题:Django实现验证码

      本文链接:https://www.haomeiwen.com/subject/fiaymttx.html