摘要:理論原理同樣是簽發(fā),只不過這次由服務(wù)端來簽發(fā),然后將通過發(fā)送給客戶端,客戶端需要先取到圖片資源,注意這里返回的應(yīng)該是一個合法的二進制流,然后從中取出,同時展示給用戶。
前言
在傳統(tǒng)的 Web 開發(fā)過程中,處理圖形驗證碼很簡單,只需要在后臺用隨機字符串生成一個圖片,將驗證碼內(nèi)容放進 Session 即可,用戶提交表單時從 Session[1] 取出判斷即可。
但是現(xiàn)如今,越來越推崇 API 交互,無狀態(tài),在 Session 這一塊,雖然默認(rèn)配置是不支持了,但是還是有很多曲線救國的方法。
基于 Session 實現(xiàn)在 API 開發(fā)中,我們也可以給前端簽發(fā) SessionID ,并且通過 PHP 的內(nèi)置方法,來實現(xiàn)這一切。
比如 我們與前段約定,當(dāng)在請求中包含有 X-Session-Id ,且不為空時,表示這個會話已經(jīng)注冊過 SessionID ,否則就頒布一個 SessionID 并返回在 Response Header 中的 X-Session-Id 讓前段記錄這個 SessionID ,下面簡單實現(xiàn)一下。
// code_session.php session_start(); // 這里假設(shè)已經(jīng)通過 Header 獲取到了 SessionID,并保存到了 $sessionId 變量中。 // 當(dāng) SessionID 不存在,或者 為空 則創(chuàng)建新的 SessionID 。 if(!isset($sessionId) || empty($sessionId)){ $sessionId = session_create_id(); // 因為前臺還沒有 SessionID ,所以下發(fā)一個,通知前端保存。 header("X-Session-Id: ".$sessionId); } // 設(shè)置當(dāng)前會話的 SessionID 。 session_id($sessionId); // 這里我們就可以自由的讀寫 Session 了。 // 生成驗證碼 $code = mt_rand(1e3 ,1e4-1); // create_image 請自行實現(xiàn) 或者使用現(xiàn)有的圖形驗證碼庫生成。 $image = create_image($code); // 存儲進去 Session $_SESSION["code"] = $code; // 輸出一張圖片 $image->output();
上面基本實現(xiàn)了生成圖片,前端需要根據(jù) 只需要再提交表單時,在 headers 中帶上 X-Session-ID 即可。
// code_session_validate.php session_start(); // 這里假設(shè)已經(jīng)通過 Header 獲取到了 SessionID,并保存到了 $sessionId 變量中。 // 當(dāng) SessionID 不存在,或者 為空 則創(chuàng)建新的 SessionID 。 if( !isset($sessionId) || empty($sessionId) || !isset($_POST["code"]) || empty($_POST["code"]) ){ // 因為沒有提交 SessionID 過來 這個肯定就是不成立的了,所以直接終止即可。 exit; } // 設(shè)置當(dāng)前會話的 SessionID 。 session_id($sessionId); if($_POST["code"]!=$_SESSION["code"]){ // 驗證碼錯誤啦 exit; } // 驗證通過了就刪掉 code, unset($_SESSION["code"]);
上面使用 Session ,我們基本就實現(xiàn)了一個簡單的驗證,而且是基于 API 交互的,不依賴瀏覽器 cookie 。當(dāng)我們需要一些復(fù)雜的比如共享 Session ,這些就不在本文的討論范圍了(其實現(xiàn)在也已經(jīng)超綱了)
基于客戶端主動簽發(fā)接下來的方法是無狀態(tài)的,但是需要用到 Redis 。這里使用 PHPRedis 這個擴展來處理。
在大多數(shù)情況下,我們并不需要像上面使用 Session 那樣來創(chuàng)建過多的 Session ,造成有一些資源浪費,當(dāng)然,Session 可以做的不止這些,下面我們就用 Redis 來做一個客戶端主動簽發(fā) 的圖片驗證碼。
理論原理由客戶端本地生成隨機字符串,然后拼接在獲取驗證碼地址的后面,后端截取客戶端生成的隨機字符串,用此作為驗證憑證放入 Redis 中去,再客戶端提交時需要帶上先前生成的隨機字符串一同進項驗證。
// code_client.php $salt = "wertyujkdbaskndasda"; if(!isset($_GET["sign"])){ // 客戶端沒有提供簽名,停止執(zhí)行 exit; } // 用戶傳來的一切數(shù)據(jù)都是不可靠的,我們需要對其加鹽后執(zhí)行 md5 $sign = md5($_GET["sign"].$salt); // 拼接上簽名作為 Redis 的 key $key = "code:".$sign; // 連接 Redis $cache = new Redis(); // 生成驗證碼 $code = mt_rand(1e3,1e4-1); // 保存驗證碼到 Redis 并設(shè)置2分鐘的有效期。 if($cache->exists($key)){ // 這個 Key 已經(jīng)被占用了,這里先停止。 exit; } $cache->set($key,$code,60*2); // 創(chuàng)建圖片并返回 $image = create_image($code); $image->output();
好了,接下來驗證一下。
// code_client_validate.php $salt = "wertyujkdbaskndasda"; if( !isset($_POST["sign"]) || !isset($_POST["code"]) // 沒有提交驗證碼過來。 || !empty($_POST["code"]) ){ // 客戶端沒有提供簽名,停止執(zhí)行 exit; } // 用戶傳來的一切數(shù)據(jù)都是不可靠的,我們需要對其加鹽后執(zhí)行 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);
看著是不是要復(fù)雜點兒,甚至還用上了 Redis ,雖然看著不咋地,但是他也實現(xiàn)了我們想要的,不過這個也不算是太好的方案,而且,還要考慮客戶端字符串不夠隨機的情況,接下來我們改變一下方向,換成服務(wù)端簽發(fā)。
基于服務(wù)端簽發(fā)剛剛的是基于客戶端簽發(fā)的實現(xiàn),下面來提供另一種思路,但是大體上,這個是差不多的哈都。
理論原理同樣是簽發(fā) Sign ,只不過這次由服務(wù)端來簽發(fā),然后將 Sign 通過 Header 發(fā)送給客戶端,客戶端需要先取到圖片資源,注意這里返回的應(yīng)該是一個合法的二進制流,然后從 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 并設(shè)置2分鐘的有效期。 $cache->set($key,$code,60*2); // 創(chuàng)建圖片并返回 $image = create_image($code); // 哈哈 要剃掉前綴喲 header("X-Captcha-Sign: " . str_replace("code:","",$key)); $image->output();
看起來幾乎沒有變化,只是生成 Sign 的方式變了一下,但是,這樣搞的話,前端同學(xué)可能就不爽了,他們要先獲取這個資源和 headers 中的 X-Captcha-Sign 再 show 到界面上,當(dāng)然 可以直接將結(jié)果 base64 或者 直接用用二進制流生成位圖顯示都是可以的,我們只是需要可以驗證,驗證方法直接使用上面的即可。
特別注意當(dāng)你使用 ajax 獲取這個資源是,如果你的業(yè)務(wù)涉及到了跨域,你還需要在響應(yīng)頭設(shè)置 Access-Control-Expose-Headers - HTTP | MDN,否則 ajax 無法獲取自定義的響應(yīng)頭。。
header("Access-Control-Expose-Headers: X-Captcha-Sign");總結(jié)
看了這三種解決方案,基本都能滿足我們的需求,可能還有人想到了另一種方案。提供一個 json 接口名,在后臺生成圖片然后保存起來,返回 url 和 sign 給前端,這樣就好了,但是這樣做,我們的資源并不太可控,會造成一定的資源浪費,這里我并沒有考慮 這種方案。
文中所提到的一些知識都是對一些基礎(chǔ)知識的應(yīng)用,文章中的代碼是寫文章直接敲的,如果有排版錯誤或者邏輯錯誤,請不吝賜教。
文中所用到的 Redis 為 PHPRedis 擴展。至于驗證碼圖片生成可以用 gregwar/captcha - Packagist 來做喲。
以上只是我個人的一些理解,如果你有更好的方案,不妨一起分享。
參考PHP: Sessions - Manual
[注1] 本文中所提到的 Session 為一種技術(shù)標(biāo)準(zhǔn)和和我們常說的通過瀏覽器自動傳遞 Cookie 交互中的 Session 有一定概念卻別,這里只是自己手動實現(xiàn)了 SessionID的傳遞 ,但是始終保持 Session 的直譯語義 “會話”。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/31736.html
摘要:小練習(xí)作者本文首發(fā)博客功能基于進行登錄,注冊,留言的簡單網(wǎng)站。所以這個小練習(xí),從一個簡單的方面入手,希望能給踩過同樣多坑的同路人一點啟發(fā)。就意味著要重新登錄。的作用是進行進程守護,當(dāng)你的意外的停止的時候,進行重啟。 Vue+Koa+Mongodb 小練習(xí) 作者: Pawn 本文首發(fā): Pawn博客 功能: 基于vue koa mongodb進行登錄,注冊,留言的簡單網(wǎng)站。 體驗地址: ...
摘要:巔峰人生年老兵思路上的轉(zhuǎn)變,遠比單純提升技術(shù)更有價值本文節(jié)選自趙成教授在極客時間開設(shè)的趙成的運維體系管理課,是其對自己十年技術(shù)生涯的回顧與總結(jié)。趙成教授來自美麗聯(lián)合集團,集團旗下兩大主力產(chǎn)品是蘑菇街和美麗說,目前負(fù)責(zé)管理集團的技術(shù)服務(wù)團隊。 showImg(https://segmentfault.com/img/remote/1460000012476504?w=1240&h=826...
閱讀 994·2023-04-25 15:42
閱讀 3584·2021-11-02 14:38
閱讀 2886·2021-09-30 09:48
閱讀 1419·2021-09-23 11:22
閱讀 3379·2021-09-06 15:02
閱讀 3186·2021-09-04 16:41
閱讀 607·2021-09-02 15:41
閱讀 2012·2021-08-26 14:13