在我们网上冲浪的时候验证码的存在就像是空气一般的存在,是根本无法避免的。对于完全不了解开发的同学,他们总会嫌弃验证码拖慢他们的工作效率。但是对于我们网站的建设者来说,验证码的存在就像是一道坚固的城墙,将一些恶意行为拒之门外。验证码的存在作为一种人机识别手段,可以简单的对正常人或者机器的操作加以区分。将一些恶意行为从源头阻拦。比如说防止暴力密码破解行为。我记得之前比尔盖茨说过:“任何密码在暴力穷举面前都不堪一击”。验证码的存在可以很好的防范这种行为,初次之外还有恶意灌水、垃圾注册、恶意登录、刷票等行为。这些而已行为会破坏服务的正常运行。同时由于这些恶意操作都是机器提交的,会给网站服务器带来巨大的压力,堪比DDOS攻击,所以验证码的存在是有意义且必要的。
如何在Flask框架内实现简单的图形验证码呢?这个就是我今天研究的重点了😏。首先声明这个思路是我从别人博客借鉴的,代码也是完全照着别人的思路写的,但是学习就是这样取长补短的过程不是么?我们先来说一下验证码实现的思路:大致思路就是通过服务端随机生成验证码图片,并且将验证码内容通过session缓存至客户端,当收到客户端的表单的时候验证发送的验证码数据和session缓存中的数据是否一致来判断是否通过验证。因为session是不不对称加密的,所以不用担心会被破解。还是放张图来体会一下吧:
图形验证码的生成
验证码实现涉及到图像操作,这就不得不提到Python强大的Pillow库了。通过简单的API实现强大的图像处理,这是对Pillow库的最好总结。通过Pillow库我们可以简单的实现符合我们前端页面风格的图形验证码,比起Java的图形处理真是超级简单省心呢。生成验证码其实很简单,使用Pillow创建一个图像,并将随机生成的字符打印到图像上,最后对图像进行风格化和噪声处理就完成了。先看看我实现的风格。
from PIL import Image, ImageFont, ImageDraw, ImageFilter import random class VercCode(): random_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345789' width = 107 height = 43 @classmethod def generate_vercode(cls): # 创建一个新的图像, 设置长宽和背景颜色 img = Image.new('RGB', (cls.width, cls.height), "#f1f0f0") font = ImageFont.truetype('msyhbd.ttc', 30) draw = ImageDraw.Draw(img) vercode = "" # 生成随机验证码,并将验证码转成图像打印到图像 for item in range(4): code = random.choice(cls.random_letters) vercode += code draw.text((6 + random.randint(1, 2) + 23 * item, 2), text=code, fill=cls.__random_color(), font=font) # 画几条随机线,让验证码看起来更专业 for x in range(4): x1 = random.randint(0, cls.width // 2) y1 = random.randint(0, cls.height // 2) x2 = random.randint(0, cls.width) y2 = random.randint(cls.height // 2, cls.height) draw.line(((x1, y1), (x2, y2)), fill=cls.__random_color(), width=2) # 加上一层滤波器滤镜 img = img.filter(ImageFilter.EDGE_ENHANCE) return img, vercode.lower() @classmethod def __random_color(cls): # 随机生成一个RGB颜色值 return tuple([random.randint(64, 180) for _ in range(3)])
在Flask内实现带验证码的表单
对于表单的验证,我们只需要验证传来表单内验证码数据是否于session中缓存的数据一致。如果数据一致的话就证明验证码通过,如果验证不通过的话就执行失败操作。原理很简单,我们需要在Flask工程内添加两个视图函数:一个视图函数用于返回验证码图像信息,另一个视图函数作为验证测试表单。
@web.route("/vercode") def vercode(): image, vercode = VercCode.generate_vercode() buffer = BytesIO() image.save(buffer, "png") buffer_str = buffer.getvalue() response = make_response(buffer_str) response.headers['Content-Type'] = 'image/gif' session["vercode"] = vercode return response @web.route("/register", methods=['GET', 'POST']) def register(): form = VercodeForm(request.form) if form.validate_on_submit(): true_code = session.get("vercode") if form.vercode.data == true: flash("验证通过") return render_template(success.html", form=form) flash("验证码错误.请从新输入") return render_template("vercode.html", form=form)
两个视图函数完成之后就只差前端页面的耦合了,在这里前端不是我们重点讨论的对象,就不贴具体代码了。这样我们就实现简单的图像验证码功能了,可以说是十分简单了。可是对于验证码来说这样的安全性似乎并没有提高很多?
关于验证码的思考
上面的验证码我们虽然实现了,从表面看功能似乎齐全,看起来也蛮高大上,实则不然。验证码设计并不是这么简单的,在设计中需要注意许多规范。就好比上面设计的验证码就有一个致命的缺陷,生成的验证码可以无限复用,因为没有给验证码添加使用之后失效的规则。总之问题提出来了,解决的思路还是要后期慢慢研究啊。小小的验证码背后藏着大大的安全问题。Flask-WTF文档说支持验证码校验,但是文档写的太晦涩了我看不懂😅🐷,后面研究一下在写个总结吧,毕竟入门,毕竟菜鸡。
给几个我网上找到的博客文章,看看大神对验证码的实现有哪些思考:http://www.lijiejie.com/safe-issues-of-captcha/[图形验证码的常见安全问题],https://zhuanlan.zhihu.com/p/50433406[如何设计相对安全的图形验证码?]。两个文章总结的还是比较全面的,有时间可以好好研究一下不是?
red-tea
赞
delicate
可以分享一下全部的代码吗
Weiney
其实这已经是很完整的代码啦,你可以好好看看,这个验证码有很多bug,只是做了个样子出来
delicate
我刚开始学着做,很多还不是很懂,想跑一遍全部的代码,但是不知道这些代码贴在哪里,所以想看看你全部的代码
Weiney
如果是还在入门的话可以从文档的简单例子开始啊,又不懂的也可以加QQ一起探讨的