1. 程式人生 > >Tornado框架實現圖形驗證碼功能

Tornado框架實現圖形驗證碼功能

tor length turn 黑客 body world 工作流 params fun

圖形驗證碼是項目開發過程中經常遇到的一個功能,在很多語言中都有對應的不同形式的圖形驗證碼功能的封裝,python 中同樣也有類似的封裝操作,通過繪制生成一個指定的圖形數據,讓前端HTML頁面通過鏈接獲取到對應的圖片驗證碼進行操作。

  • 什麽是驗證碼?

驗證碼(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自動區分計算機和人類的圖靈測試)的縮寫,是一種區分用戶是計算機還是人的公共全自動程序。

  • 驗證碼的作用?

可以防止:惡意破解密碼、刷票、論壇灌水,有效防止某個黑客對某一個特定註冊用戶用特定程序暴力破解方式進行不斷的登陸嘗試,實際上用驗證碼是現在很多網站通行的方式,我們利用比較簡易的方式實現了這個功能。這個問題可以由計算機生成並評判,但是必須只有人類才能解答。由於計算機無法解答CAPTCHA的問題,所以回答出問題的用戶就可以被認為是人類。

驗證碼自從2002年提出以來,證明了它的效果後,在互聯網上得到了迅速的推廣。在發展過程中,出現了圖形驗證碼,語言驗證碼,郵件驗證碼,短信驗證碼等等。但是它們的原理大抵相同。

  • 驗證碼原理!

首先驗證碼是一個程序概念,它通過向請求的發起方提出問題,能正確回答的即使人類,反之則為機器。這個程序基於這個樣一個重要的假設:提出的問題要容易被人類解答,機器無法解答。在當時的技術條件下,識別扭曲的圖形,對於機器來說還是一個很艱難的任務,對於人來說,相對可以接受。所以最開始的驗證碼是圖形驗證碼,也是比較容易實現的驗證碼。

  • 圖形驗證碼的工作流程

我們登錄,註冊時首先會向服務器發送一個頁面請求。服務器在接到這個請求後,隨機生成一個字符串,然後將這個字符串畫成一張圖片,並將這個圖片返回給請求用戶。用戶在收到這個頁面之後再次提交請求數據時,需要識別這張圖片上的字符,並且填寫跟需要提交的數據一並提交給服務器。服務器在收到這些數據後,會首先判斷圖片上的字符串跟之前生成的字符串是否一致,一致則說明提交合法,反之不合法。

那麽我們今天通過python中的常用的web框架tornado來實現一個圖形驗證碼。通過tornado搭建一個web服務器是非常容易的。下面的代碼就是一個通過tornado實現的web服務器。

核心操作步驟:

  1. 生成圖形驗證碼【封裝】
  2. HTML頁面請求【驗證碼】
  3. 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。

技術分享圖片

那麽我們今天需要添加一個圖形驗證碼的功能。首先需要修改前端頁面如下:

技術分享圖片

Tornado框架實現圖形驗證碼功能