在傳統的Web 開發過程中,處理圖形驗證碼很簡單,只需要在後台用隨機字串產生一個圖片,將驗證碼內容放進Session 即可,使用者提交表單時從Session[1] 取出判斷即可。
但是現如今,越來越推崇 API 交互,無狀態,在 Session 這一塊,雖然預設配置是不支持了,但是還是有很多曲線救國的方法。
在 API 開發中,我們也可以給前端簽發 SessionID ,並且透過 PHP 的內建方法,來實現這一切。
例如我們與前段約定,當在請求中包含有X-Session-Id
,且不為空時,表示這個會話已經註冊過SessionID ,否則就頒布一個SessionID 並返回在Response Header 中的X-Session-Id
讓前段記錄這個SessionID ,以下簡單實作一下。
// 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
即可。
// 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 中去,再客戶端提交時需要帶上先前產生的隨機字串一同進項驗證。
// 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();
好了,接下來驗證一下。
// 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 ,雖然看著不咋地,但是他也實現了我們想要的,不過這個也不算是太好的方案,而且,也要考慮客戶端字串不夠隨機的情況,接下來我們改變一下方向,換成服務端簽發。
剛剛的是基於客戶端簽發的實現,下面來提供另一種思路,但是大體上,這個是差不多的哈都。
同樣是簽發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 的直譯語義「會話」。
更多PHP相關技術文章,請造訪PHP教學欄位學習!
以上是API 互動中怎麼做好圖片驗證碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!