图形验证码是项目开发过程中经常遇到的一个功能,在很多语言中都有对应的不同形式的图形验证码功能的封装,python 中同样也有类似的封装操作,通过绘制生成一个指定的图形数据,让前端HTML页面通过链接获取到对应的图片验证码进行操作。
- 什么是验证码?
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。
- 验证码的作用?
可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上用验证码是现在很多网站通行的方式,我们利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
验证码自从2002年提出以来,证明了它的效果后,在互联网上得到了迅速的推广。在发展过程中,出现了图形验证码,语言验证码,邮件验证码,短信验证码等等。但是它们的原理大抵相同。
- 验证码原理!
首先验证码是一个程序概念,它通过向请求的发起方提出问题,能正确回答的即使人类,反之则为机器。这个程序基于这个样一个重要的假设:提出的问题要容易被人类解答,机器无法解答。在当时的技术条件下,识别扭曲的图形,对于机器来说还是一个很艰难的任务,对于人来说,相对可以接受。所以最开始的验证码是图形验证码,也是比较容易实现的验证码。
- 图形验证码的工作流程
我们登录,注册时首先会向服务器发送一个页面请求。服务器在接到这个请求后,随机生成一个字符串,然后将这个字符串画成一张图片,并将这个图片返回给请求用户。用户在收到这个页面之后再次提交请求数据时,需要识别这张图片上的字符,并且填写跟需要提交的数据一并提交给服务器。服务器在收到这些数据后,会首先判断图片上的字符串跟之前生成的字符串是否一致,一致则说明提交合法,反之不合法。
那么我们今天通过python中的常用的web框架tornado来实现一个图形验证码。通过tornado搭建一个web服务器是非常容易的。下面的代码就是一个通过tornado实现的web服务器。
核心操作步骤:
- 生成图形验证码【封装】
- HTML页面请求【验证码】
- tornado handler中进行处理
1. 生成图形验证码
这里我们通过PIL模块的图形绘制操作完成核心的验证码 功能
首先安装PIL模块:
> pip install PIL
很遗憾,上面的命令执行不会成功,PIL库是Pillow图像库的一部分,已经迁移了地址,执行如下命令进行安装:
> easy_install Pillow
其次,开发图形验证码的函数封装(VerifyCode.py):
在初始化数据函数中,通过font_type指定了具体的字体文件名称,在使用时要将对应的
corbel.ttf
字体文件拷贝到VerifyCode.py
同级目录下
- VerifyCode.py文件
#coding:utf-8 import random from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ‘‘.join(map(str, range(3, 10))) # 数字 init_chars = ‘‘.join((_letter_cases, _upper_cases, _numbers)) def create_validate_code(size=(120, 30), chars=init_chars, img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=18, font_type="corbel.ttf", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance = 2): ‘‘‘ @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 ‘‘‘ width, height = size # 宽, 高 img = Image.new(mode, size, bg_color) # 创建图形 draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): ‘‘‘生成给定长度的字符串,返回列表格式‘‘‘ return random.sample(chars, length) def create_lines(): ‘‘‘绘制干扰线‘‘‘ line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) #结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): ‘‘‘绘制干扰点‘‘‘ chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): ‘‘‘绘制验证码字符‘‘‘ c_chars = get_chars() strs = ‘ %s ‘ % ‘ ‘.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3), strs, font=font, fill=fg_color) return ‘‘.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs
2. server.py文件
# -*- coding:utf-8 -*- import tornado.web import tornado.ioloop import tornado.httpserver import io import VerifyCode #定义一个路由类 class indexHandler(tornado.web.RequestHandler): #添加一个处理get请求方式的方法 def get(self): #向响应中添加数据 # self.write(‘hello,tornado,my name is get...‘) self.render(‘index.html‘) def post(self, *args, **kwargs): # self.write(‘hello tornado my name is post...‘) username=self.get_argument(‘user‘) password=self.get_argument(‘pwd‘) if username==‘admin‘ and password==‘123‘: self.write(‘登录成功‘) else: self.write(‘用户密码错误‘) class CheckCodeHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): #创建一个文件流 imgio=io.BytesIO() #生成图片对象和对应字符串 img,code=VerifyCode.create_validate_code() #将图片信息保存到文件流 img.save(imgio,‘GIF‘) #返回图片 self.write(imgio.getvalue()) if __name__ == ‘__main__‘: # 创建一个APP,并注册路由 app=tornado.web.Application([(r‘/index‘,indexHandler), (r‘/check_code‘,CheckCodeHandler), ]) #监听端口,HTTP服务 http_server=tornado.httpserver.HTTPServer(app) #原始方式 http_server.bind(8888) http_server.start(1) # 监听本地端口8888 #http_server.listen(8888) # 启动服务 tornado.ioloop.IOLoop.current().start()
3. 页面index.html文件
后端程序已经准备完成,接下来的操作,就是在前端网页中,通过img标签来加载对应的图形验证码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #imgCode{width: 100px;height: 40px;border: 1px solid red;} </style> </head> <body> <form action="/index" method="post" enctype="multipart/form-data"> <p><input name="user" type="text" placeholder="用户"></p> <p><input name="pwd" type="password" placeholder="密码"></p> <p><input name="code" type="text" placeholder="验证码"> ![](/check_code) </p> <p><input type="submit" value="submit"></p> </form> <script> function ChangeCode() { var code=document.getElementById(‘imgCode‘); // 每次更新get地址,防止出现get请求缓存问题 code.src="/check_code?rd=" + new Date() } </script> </body> </html>
4. 运行程序,查看结果
打开浏览器,访问指定的登录页面,验证码如下图所示
5.详解
这段是路由规则,r”/index”是规则,支持正则表达式。这条路由代表,url为“/index”的请求指向IndexHandler。那么我们在浏览器中访问127.0.0.1:8080/index的时候,浏览器的请求就会交给IndexHandler来响应。
业务处理模块,也是我们开发工作的核心。每一个类对应一个业务功能,所有的类必须继承tornado.web.RequestHandler类,这个类是tornado中用来处理请求的类。上面讲到浏览器访问”/index”,这个请求会被路由转发到IndexHandler类。因为是get请求,所以会执行get方法。Self.write(‘hello world!’),会返回‘hello world!’字符串。所以浏览器也会接收到这个字符串。
首先我们需要在服务器端写一个登录的html文件。
为了简单,直接将这个文件命名为index.html放到当前目录。我们需要修改get方法中的代码。
self.render(‘index.html’)会返回‘index.html’页面
在index.html中form表单会向action指向的url发送post请求。
post请求的url是”/index”,所以我们需要在IndexHandler中再写一个post方法,来处理登录。
Self.get_argument(‘user’)可以获取post请求中发过来的数据,参数user对应html中form标签里的元素的name。
那么我们今天需要添加一个图形验证码的功能。首先需要修改前端页面如下: