在傳統的 Web 開發過程當中,處理圖形驗證碼很簡單,只須要在後臺用隨機字符串生成一個圖片,將驗證碼內容放進 Session 便可,用戶提交表單時從 Session[1] 取出判斷便可。php
可是現現在,愈來愈推崇 API 交互,無狀態,在 Session 這一塊,雖然默認配置是不支持了,可是仍是有不少曲線救國的方法。前端
在 API 開發中,咱們也能夠給前端簽發 SessionID ,而且經過 PHP 的內置方法,來實現這一切。
好比 咱們與前段約定,當在請求中包含有 X-Session-Id
,且不爲空時,表示這個會話已經註冊過 SessionID ,不然就頒佈一個 SessionID 並返回在 Response Header 中的 X-Session-Id
讓前段記錄這個 SessionID ,下面簡單實現一下。ajax
// code_session.php session_start(); // 這裏假設已經經過 Header 獲取到了 SessionID,並保存到了 $sessionId 變量中。 // 當 SessionID 不存在,或者 爲空 則建立新的 SessionID 。 if(!isset($sessionId) || empty($sessionId)){ $sessionId = session_create_id(); // 由於前臺尚未 SessionID ,因此下發一個,通知前端保存。 header('X-Session-Id: '.$sessionId); } // 設置當前會話的 SessionID 。 session_id($sessionId); // 這裏咱們就能夠自由的讀寫 Session 了。 // 生成驗證碼 $code = mt_rand(1e3 ,1e4-1); // create_image 請自行實現 或者使用現有的圖形驗證碼庫生成。 $image = create_image($code); // 存儲進去 Session $_SESSION['code'] = $code; // 輸出一張圖片 $image->output();
上面基本實現了生成圖片,前端須要根據 只須要再提交表單時,在 headers 中帶上 X-Session-ID
便可。json
// code_session_validate.php session_start(); // 這裏假設已經經過 Header 獲取到了 SessionID,並保存到了 $sessionId 變量中。 // 當 SessionID 不存在,或者 爲空 則建立新的 SessionID 。 if( !isset($sessionId) || empty($sessionId) || !isset($_POST['code']) || empty($_POST['code']) ){ // 由於沒有提交 SessionID 過來 這個確定就是不成立的了,因此直接終止便可。 exit; } // 設置當前會話的 SessionID 。 session_id($sessionId); if($_POST['code']!=$_SESSION['code']){ // 驗證碼錯誤啦 exit; } // 驗證經過了就刪掉 code, unset($_SESSION['code']);
上面使用 Session ,咱們基本就實現了一個簡單的驗證,並且是基於 API 交互的,不依賴瀏覽器 cookie 。當咱們須要一些複雜的好比共享 Session ,這些就不在本文的討論範圍了(其實如今也已經超綱了)後端
接下來的方法是無狀態的,可是須要用到 Redis 。這裏使用 PHPRedis 這個擴展來處理。跨域
在大多數狀況下,咱們並不須要像上面使用 Session 那樣來建立過多的 Session ,形成有一些資源浪費,固然,Session 能夠作的不止這些,下面咱們就用 Redis 來作一個客戶端主動簽發
的圖片驗證碼。瀏覽器
由客戶端本地生成隨機字符串,而後拼接在獲取驗證碼地址的後面,後端截取客戶端生成的隨機字符串,用此做爲驗證憑證放入 Redis 中去,再客戶端提交時須要帶上先前生成的隨機字符串一同進項驗證。cookie
// code_client.php $salt = 'wertyujkdbaskndasda'; if(!isset($_GET['sign'])){ // 客戶端沒有提供簽名,中止執行 exit; } // 用戶傳來的一切數據都是不可靠的,咱們須要對其加鹽後執行 md5 $sign = md5($_GET['sign'].$salt); // 拼接上簽名做爲 Redis 的 key $key = 'code:'.$sign; // 鏈接 Redis $cache = new \Redis(); // 生成驗證碼 $code = mt_rand(1e3,1e4-1); // 保存驗證碼到 Redis 並設置2分鐘的有效期。 if($cache->exists($key)){ // 這個 Key 已經被佔用了,這裏先中止。 exit; } $cache->set($key,$code,60*2); // 建立圖片並返回 $image = create_image($code); $image->output();
好了,接下來驗證一下。session
// code_client_validate.php $salt = 'wertyujkdbaskndasda'; if( !isset($_POST['sign']) || !isset($_POST['code']) // 沒有提交驗證碼過來。 || !empty($_POST['code']) ){ // 客戶端沒有提供簽名,中止執行 exit; } // 用戶傳來的一切數據都是不可靠的,咱們須要對其加鹽後執行 md5 $sign = md5($_POST['sign'].$salt); // 拼接上簽名做爲 Redis 的 key $key = 'code:'.$sign; // 鏈接 Redis $cache = new \Redis(); if(!$cache->exists($key)){ // 根本沒有這個 key eixt; } if($cache->get($key)!=$_POST['code']){ // 驗證碼錯誤 } // 驗證經過了就刪除 $cache->del($key);
看着是否是要複雜點兒,甚至還用上了 Redis ,雖然看着不咋地,可是他也實現了咱們想要的,不過這個也不算是太好的方案,並且,還要考慮客戶端字符串不夠隨機的狀況,接下來咱們改變一下方向,換成服務端簽發。url
剛剛的是基於客戶端簽發的實現,下面來提供另外一種思路,可是大致上,這個是差很少的哈都。
一樣是簽發 Sign ,只不過此次由服務端來簽發,而後將 Sign 經過 Header 發送給客戶端,客戶端須要先取到圖片資源,注意這裏返回的應該是一個合法的二進制流,而後從 header 中取出 Sign ,同時展現給用戶。
// code_server.php $cache = new \Redis(); $salt = 'wertyujkdbaskndasda'; function generateSign(){ global $cache,$salt; $sign = md5(mt_rand().$salt); // 拼接上簽名做爲 Redis 的 key $key = 'code:'.$sign; if($cache->exists($key)){ // 是的 你麼有看錯,就是若是生成的 Sign 已存在,就進行遞歸,直到生成出一個不存在的。 return generateSign(); } return $key; } // 鏈接 Redis $key = generateSign(); // 生成驗證碼 $code = mt_rand(1e3,1e4-1); // 保存驗證碼到 Redis 並設置2分鐘的有效期。 $cache->set($key,$code,60*2); // 建立圖片並返回 $image = create_image($code); // 哈哈 要剃掉前綴喲 header('X-Captcha-Sign: ' . str_replace('code:','',$key)); $image->output();
看起來幾乎沒有變化,只是生成 Sign 的方式變了一下,可是,這樣搞的話,前端同窗可能就不爽了,他們要先獲取這個資源和 headers 中的 X-Captcha-Sign
再 show 到界面上,固然 能夠直接將結果 base64 或者 直接用用二進制流生成位圖顯示都是能夠的,咱們只是須要能夠驗證,驗證方法直接使用上面的便可。
當你使用 ajax 獲取這個資源是,若是你的業務涉及到了跨域,你還須要在響應頭設置 Access-Control-Expose-Headers - HTTP | MDN,不然 ajax 沒法獲取自定義的響應頭。。
header('Access-Control-Expose-Headers: X-Captcha-Sign');
看了這三種解決方案,基本都能知足咱們的需求,可能還有人想到了另外一種方案。提供一個 json 接口名,在後臺生成圖片而後保存起來,返回 url 和 sign 給前端,這樣就行了,可是這樣作,咱們的資源並不太可控,會形成必定的資源浪費,這裏我並無考慮 這種方案。
文中所提到的一些知識都是對一些基礎知識的應用,文章中的代碼是寫文章直接敲的,若是有排版錯誤或者邏輯錯誤,請不吝賜教。
文中所用到的 Redis 爲 PHPRedis 擴展。至於驗證碼圖片生成能夠用 gregwar/captcha - Packagist 來作喲。
以上只是我我的的一些理解,若是你有更好的方案,不妨一塊兒分享。
[注1] 本文中所提到的 Session 爲一種技術標準和和咱們常說的經過瀏覽器自動傳遞 Cookie 交互中的 Session 有必定概念卻別,這裏只是本身手動實現了 SessionID的傳遞 ,可是始終保持 Session 的直譯語義 「會話」。