wecenter學習筆記-驗證碼管理

該文是wecenter學習筆記的一部分

驗證碼管理

驗證需要考慮

  • 驗證碼圖片的生成、存儲和訪問
  • 驗證碼的有效期管理
  • 失效驗證碼的清理策略
  • 驗證有效性

基于 Zend_Captcha_Image 實現的驗證碼生成和管理功能,可以根據自己提供的字體文件生成自定義大小和位數的驗證碼,
并存儲到單獨的session命名空間中。

system/core/captcha.php#__construct

$this->captcha = new Zend_Captcha_Image(array(
    'font' => $this->get_font(),
    'imgdir' => $img_dir,
    'fontsize' => rand(20, 22),
    'width' => 100,
    'height' => 40,
    'wordlen' => 4,
    'session' => new Zend_Session_Namespace(G_COOKIE_PREFIX . '_Captcha'),
    'timeout' => 600
));

$this->captcha->setDotNoiseLevel(rand(3, 6));
$this->captcha->setLineNoiseLevel(rand(1, 2));

** 使用方式和實現原理 **

生成驗證碼

AWS_APP::captcha()->generate();

system/core/captcha.php#generate

public function generate()
{
    $this->captcha->generate();

    HTTP::no_cache_header();

    readfile($this->captcha->getImgDir() . $this->captcha->getId() . $this->captcha->getSuffix());

    die;
}

檢查驗證碼

if (!AWS_APP::captcha()->is_validate($_POST['seccode_verify']) AND get_setting('register_seccode') == 'Y')
{
    H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t('請填寫正確的驗證碼')));
}

system/core/captcha.php#is_validate

public function is_validate($validate_code, $generate_new = true)
{
    if (strtolower($this->captcha->getWord()) == strtolower($validate_code))
    {
        if ($generate_new)
        {
            $this->captcha->generate();
        }
        
        return true;
    }

    return false;
}

Zend_Captcha_Image

** 生成驗證碼圖片 **

Zend/Captcha/Image.php

public function generate()
{
    $id = parent::generate();
    $tries = 5;
    // If there's already such file, try creating a new ID
    while($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) {
        $id = $this->_generateRandomId();
        $this->_setId($id);
    }
    $this->_generateImage($id, $this->getWord());

    if (mt_rand(1, $this->getGcFreq()) == 1) {
        $this->_gc();
    }
    return $id;
}

Zend/Captcha/Image.php

protected function _generateImage($id, $word)
{
    if (!extension_loaded("gd")) {
        //require_once 'Zend/Captcha/Exception.php';
        throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension");
    }

    if (!function_exists("imagepng")) {
        //require_once 'Zend/Captcha/Exception.php';
        throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support");
    }

    if (!function_exists("imageftbbox")) {
        //require_once 'Zend/Captcha/Exception.php';
        throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support");
    }

    $font = $this->getFont();

    if (empty($font)) {
        //require_once 'Zend/Captcha/Exception.php';
        throw new Zend_Captcha_Exception("Image CAPTCHA requires font");
    }

    $w     = $this->getWidth();
    $h     = $this->getHeight();
    $fsize = $this->getFontSize();

    $img_file   = $this->getImgDir() . $id . $this->getSuffix();
    if(empty($this->_startImage)) {
        $img        = imagecreatetruecolor($w, $h);
    } else {
        $img = imagecreatefrompng($this->_startImage);
        if(!$img) {
            //require_once 'Zend/Captcha/Exception.php';
            throw new Zend_Captcha_Exception("Can not load start image");
        }
        $w = imagesx($img);
        $h = imagesy($img);
    }
    $text_color = imagecolorallocate($img, 0, 0, 0);
    $bg_color   = imagecolorallocate($img, 255, 255, 255);
    imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color);
    $textbox = imageftbbox($fsize, 0, $font, $word);
    $x = ($w - ($textbox[2] - $textbox[0])) / 2;
    $y = ($h - ($textbox[7] - $textbox[1])) / 2;
    imagefttext($img, $fsize, 0, $x, $y, $text_color, $font, $word);

   // generate noise
    for ($i=0; $i<$this->_dotNoiseLevel; $i++) {
       imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color);
    }
    for($i=0; $i<$this->_lineNoiseLevel; $i++) {
       imageline($img, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color);
    }

    // transformed image
    $img2     = imagecreatetruecolor($w, $h);
    $bg_color = imagecolorallocate($img2, 255, 255, 255);
    imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color);
    // apply wave transforms
    $freq1 = $this->_randomFreq();
    $freq2 = $this->_randomFreq();
    $freq3 = $this->_randomFreq();
    $freq4 = $this->_randomFreq();

    $ph1 = $this->_randomPhase();
    $ph2 = $this->_randomPhase();
    $ph3 = $this->_randomPhase();
    $ph4 = $this->_randomPhase();

    $szx = $this->_randomSize();
    $szy = $this->_randomSize();

    for ($x = 0; $x < $w; $x++) {
        for ($y = 0; $y < $h; $y++) {
            $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx;
            $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy;

            if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) {
                continue;
            } else {
                $color    = (imagecolorat($img, $sx, $sy) >> 16)         & 0xFF;
                $color_x  = (imagecolorat($img, $sx + 1, $sy) >> 16)     & 0xFF;
                $color_y  = (imagecolorat($img, $sx, $sy + 1) >> 16)     & 0xFF;
                $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF;
            }
            if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) {
                // ignore background
                continue;
            } elseif ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) {
                // transfer inside of the image as-is
                $newcolor = 0;
            } else {
                // do antialiasing for border items
                $frac_x  = $sx-floor($sx);
                $frac_y  = $sy-floor($sy);
                $frac_x1 = 1-$frac_x;
                $frac_y1 = 1-$frac_y;

                $newcolor = $color    * $frac_x1 * $frac_y1
                          + $color_x  * $frac_x  * $frac_y1
                          + $color_y  * $frac_x1 * $frac_y
                          + $color_xy * $frac_x  * $frac_y;
            }
            imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor));
        }
    }

    // generate noise
    for ($i=0; $i<$this->_dotNoiseLevel; $i++) {
        imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color);
    }
    for ($i=0; $i<$this->_lineNoiseLevel; $i++) {
       imageline($img2, mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h), $text_color);
    }

    imagepng($img2, $img_file);
    imagedestroy($img);
    imagedestroy($img2);
}

基本步驟

  1. 使用基底圖生成緩沖圖片
  2. 輸出驗證碼文本
  3. 根據點和線的噪音級別生成噪音圖形
  4. 進行波形扭曲變換
  5. 再次生成噪聲點和線
  6. 保存圖片到PNG格式的文件

** 驗證碼管理 **

驗證碼管理的基礎工作在 Zend_Captcha_Word中完成,基本實現思路

  • 將生成的驗證碼code存入session中

    Zend/Captcha/Word.php

    public function generate()
    {
        if (!$this->_keepSession) {
            $this->_session = null;
        }
        $id = $this->_generateRandomId();
        $this->_setId($id);
        $word = $this->_generateWord();
        $this->_setWord($word);
        return $id;
    }
    
    protected function _setWord($word)
    {
        $session       = $this->getSession();
        $session->word = $word;
        $this->_word   = $word;
        return $this;
    }
    
  • 驗證時直接從session中讀取并驗證

    Zend/Captcha.Word.php

    public function isValid($value, $context = null)
    {
        ...
    
        $this->_id = $value['id'];
        if ($input !== $this->getWord()) {
            $this->_error(self::BAD_CAPTCHA);
            return false;
        }
    
        return true;
    }
    
    public function getWord()
    {
        if (empty($this->_word)) {
            $session     = $this->getSession();
            $this->_word = $session->word;
        }
        return $this->_word;
    }
    
  • 清除session中的驗證碼

    通過控制Session數據失效時間和步數來清除Session中的驗證碼

    Zend/Captcha.Word.php

    public function getSession()
    {
        if (!isset($this->_session) || (null === $this->_session)) {
            $id = $this->getId();
            if (!class_exists($this->_sessionClass)) {
                //require_once 'Zend/Loader.php';
                Zend_Loader::loadClass($this->_sessionClass);
            }
            $this->_session = new $this->_sessionClass('Zend_Form_Captcha_' . $id);
            $this->_session->setExpirationHops(1, null, true);
            $this->_session->setExpirationSeconds($this->getTimeout());
        }
        return $this->_session;
    }
    

** 清理失效驗證碼圖片 **

遍歷驗證碼存儲的文件夾,按創建世界刪除有效期(最長有效期默認10分鐘)之外的圖片文件。

參照

Zend/Captcha/Image.php#_gc

GC在生成驗證碼的時候按概率隨機觸發

Zend/Captcha/Image.php#generate

if (mt_rand(1, $this->getGcFreq()) == 1) {
    $this->_gc();
}

除自己生成并管理驗證碼外,Zend Captcha還支持直接使用reCaptcha服務來管理驗證碼


配置參數管理 ←o→ 分頁組件的實現

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容

  • JCaptcha 簡介 CAPTCHA 全稱 Completely Automated Public Turing...
    誰在烽煙彼岸閱讀 742評論 0 0
  • 背景 驗證碼就是把一串隨機產品的數字動態生成一幅圖片,再加上干擾元素。此時用戶可以通過肉眼能識別里面的數字或者字符...
    dy2903閱讀 2,112評論 0 7
  • 控制器1.文件名不需要加后綴,全部小寫2.類名首字母大寫,繼承CI_Controller基類3.以下劃線開頭或者非...
    棟棟曉閱讀 1,405評論 1 6
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,807評論 18 139
  • Php:腳本語言,網站建設,服務器端運行 PHP定義:一種服務器端的HTML腳本/編程語言,是一種簡單的、面向對象...
    廖馬兒閱讀 2,163評論 2 38