1、項目簡介及需求php
edusoho是一套商業版的在線教育平臺,項目自己基於symfony2框架開發,如今有一款本身的APP,要求在很少修改edusoho自身代碼的基礎上,實現客戶端對PC端掃碼登陸。很少修改edusoho代碼的緣由是爲了不下次升級主版本時發生錯誤。css
2、版本信息及所需應用html
edusoho 7.5.14前端
php 5.5.25jquery
php GD庫web
memcache(本次使用memcache做爲存儲介質,可用redis代替) 點我下載ajax
phpqrcde(php生成二維碼插件)redis
3、實現思路算法
點擊頁面掃碼按鈕,向php發送一個請求,php生成二維碼並把sign驗證字符串保存至memcache中,請求成功後,頁面展現二維碼等待掃碼,並使用ajax以輪詢的方式請求後臺方法,查詢是否已掃碼。手機掃碼後訪問二維碼裏面的url連接,php保存已掃碼標識,並向ajax返回已掃碼狀態,前臺頁面提示已掃碼,並再次發送是否已確認登陸的ajax輪詢。掃碼完成後,APP端顯示是否登陸頁面,點擊確認,向後臺方法傳遞用戶名及sign驗證字符串,後臺驗證經過後,返回用戶名,sign及狀態碼至前端ajax。json
因爲需求是很少改動edusoho自身代碼,因此登陸時借用edusoho自己的登陸,即把ajax返回的用戶名填寫進用戶名框,sign填進密碼框,觸發登陸的submit按鈕,只在登陸的密碼驗證處添加了幾行代碼,把密碼驗證,改成了sign驗證。(symfony2採用的登陸方式是security配置化登陸,後面會講到在哪裏改動密碼驗證,不熟悉的能夠參考 security安全登陸)
4、實現代碼及步驟
一、生成登陸二維碼
(1)先在custom下的routing.yml配置訪問路由(其餘方式路由請自行參考配置,如下方法的路由配置再也不贅述)
(2)點擊掃碼按鈕的js代碼(能夠把key存進cookie中,本示例直接寫進了頁面的隱藏域)
//點擊掃碼彈出框 $('.scanqrcode').click(function (){ $('.login-section').hide();//隱藏登錄框 $('.qrcode').show();//彈窗 $('.timeout').hide(); $('.barcode-container.scanned .status.scanned, .barcode-container.scanned .mask').hide(); $('.login_op').show();//顯示遮罩層 //請求二維碼 $.ajax({ type: "POST", dataType: "json", url: "/login/create/qrcode", success: function (data) { if(data.status==1){ var qrcodeimg = '../../assets/img/qrcodeimg/'+data.msg+'.png'; //把key放進隱藏域 $('#key').val(data.msg); //替換二維碼 $('.qrcode-img').attr('src',qrcodeimg); //觸發定時任務,查看是否已掃碼 var inter = setInterval("is_sacn_qrcode();",3000); $('#timing').val(inter); } } }); });
(3)寫createQrcode方法生成二維碼圖片的php代碼(doString方法是生成sign字符串的算法,還請自行發明創造,生成結果每次都不同是最好的)
//生成登陸二維碼 public function CreateQrcodeAction(){ ob_start(); $url = 'http://'.$_SERVER['HTTP_HOST'];//獲取當前的url $http = $url.'/login/mobile/scan/qrcode';//確認掃碼的url方法 $key = $this->getRandom(30);//存放在memcache中的鍵值,隨機32位字符串 $_SESSION['qrcode_name'] = $key;//把key當作圖片的名字存在session裏 $sgin_data = $this->doString();//生成sign字符串的基本算法 $sgin = strrev(substr($key,0,2)) . $sgin_data;//截取前兩位並反轉 $value = $http.'?key='.$key.'&type=1';//二維碼內容 $errorCorrectionLevel = 'H';//容錯級別 $matrixPointSize = 8;//生成圖片大小 //生成二維碼圖片 \QRcode::png($value, 'qrcode.png', $errorCorrectionLevel, $matrixPointSize, 0); $logo = "assets/img/qrcodeimg_logo.png";//準備好的logo圖片 $QR = 'qrcode.png';//已經生成的原始二維碼圖 if ($logo !== FALSE) { $QR = imagecreatefromstring(file_get_contents($QR)); $logo = imagecreatefromstring(file_get_contents($logo)); $QR_width = imagesx($QR);//二維碼圖片寬度 $QR_height = imagesy($QR);//二維碼圖片高度 $logo_width = imagesx($logo);//logo圖片寬度 $logo_height = imagesy($logo);//logo圖片高度 $logo_qr_width = $QR_width / 3; $scale = $logo_width/$logo_qr_width; $logo_qr_height = $logo_height/$scale; $from_width = ($QR_width - $logo_qr_width) / 2; //從新組合圖片並調整大小 imagecopyresampled($QR, $logo, $from_width, $from_width, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height); } //輸出圖片 $img = imagepng($QR, 'assets/img/qrcodeimg/'.$key.'.png'); $return = array('status'=>0,'msg'=>''); if($img){ $mem = new \Memcache(); $mem->connect('127.0.0.1',11211); $res = json_encode(array('sign'=>$sgin,'type'=>0)); //存進memcache,過時時間三分鐘 $mem->set($key,$res,0,180);//180 $return = array('status'=>1,'msg'=>$key); } return $this->createJsonResponse($return); }
二、jquery彈出頁面二維碼並啓動ajax輪詢查詢是否掃碼
(1)顯示效果圖:
(2)請求是否掃碼的js代碼(未掃碼就一直輪詢,已掃碼關閉「查看是否已掃碼」方法,開啓「查看是否確認登陸」的方法輪詢,關閉二維碼框清除全部方法)
//查看是否已掃碼 function is_sacn_qrcode (){ $.ajax({ type: "POST", dataType: "json", url: " /login/scan/qrcode", success: function (data) { if(data.status==1){ //掃碼成功 $('.barcode-container.scanned .status.scanned, .barcode-container.scanned .mask').show(); //取消定時任務,清除cookie clearInterval($('#timing').val()); $('#timing').val(''); ////定時2秒關閉彈窗 //setTimeout(function(){ // $('.qrcode').hide(); //},2000); //查看是否已確認登陸 var is_login = setInterval("is_login();",3000); $('#is_login').val(is_login); //$.cookie('is_login', is_login); }else if(data.status==2){ $('.timeout,.mask').show(); //取消定時任務,清除cookie clearInterval($('#timing').val()); $('#timing').val(''); } } }); }
(3)查看是否已掃碼的php代碼
/** * 查看是否已掃碼 */ public function isScanQrcodeAction(){ $key = $_SESSION['qrcode_name']; $mem = new \Memcache(); $mem->connect('127.0.0.1',11211); $data = json_decode($mem->get($key),true); if(empty($data)){ $return = array('status'=>2,'msg'=>'已過時'); }else{ if($data['type']){ $return = array('status'=>1,'msg'=>'成功'); }else{ $return = array('status'=>0,'msg'=>''); } } return $this->createJsonResponse($return); }
(4)客戶端掃碼的php代碼
//移動設備掃碼 public function mobileScanQrcodeAction(Request $request,$key){ $key = $_GET['key']; $url = 'http://'.$_SERVER['HTTP_HOST']; $agent=$_SERVER["HTTP_USER_AGENT"]; if (!(strpos($agent, 'MicroMessenger') === false)) { // 獲取版本號 //preg_match('/.*?(MicroMessenger\/([0-9.]+))\s*/', $agent, $matches); $app_url = 'http://club.risecenter.com/wap_app.html'; // 微信瀏覽器,跳轉至下載APP頁面(可判斷非指定app瀏覽器都跳轉至此頁面) return $this->redirect($app_url); } $http = $url.'/login/qrcodedoLogin';//返回確認登陸的連接 $mem = new \Memcache(); $mem->connect('127.0.0.1',11211); $data = json_decode($mem->get($key),true); $data['type']=1;//增長type值,用來判斷是否已掃碼 $res = json_encode($data); $mem->set($key,$res,0,180); $http = $http.'?key='.$key.'&type=scan'; $return = array('status'=>1,'msg'=>$http); return $this->createJsonResponse($return); }
三、掃碼成功後判斷是否確認登陸
(1)掃碼成功效果圖:
(2)掃碼成功後查詢是否登陸js代碼(客戶端確認登陸後把用戶名傳遞給ajax,js把用戶名和sign填到用戶名和密碼錶單,觸發頁面隱藏的submit登陸按鈕)
//查看是否已確認登陸 function is_login(){ var key = $('#key').val(); $.ajax({ type: "POST", dataType: "json", url: "/login/entry/login", data:{ key:key }, success: function (data) { if(data.status==1){ var uid = data.uid; var sign = data.sign; //取消定時任務,清除cookie clearInterval($('#is_login').val()); $('#is_login').val(''); //隱藏掃碼成功 $('.barcode-container.scanned .status.scanned, .barcode-container.scanned .mask').hide(); //彈出已確認 $('.confirmed,.mask').show(); //定時1秒確認登錄 setTimeout(function(){ //確認登陸 $('#login_username').val(data.user); $('#login_password').val(data.sign); $('#login-form').submit(); },1000); }else if(data.status==2){ //取消定時任務,清除cookie clearInterval($('#is_login').val()); $('#is_login').val(''); alert(data.msg); } } }); }
(3)查詢是否已確認登陸的php代碼
/** * 客戶端掃碼後登陸 * $sign 客戶端掃碼時傳遞標識,與memcache中的作對比 * $key 網頁端二維碼中傳遞的key * $uid 客戶端登錄後掃碼傳遞的用戶id * @return void */ public function qrcodeDoLoginAction(Request $request,$login,$key,$sign){ $login = $_GET['login']; $key = $_GET['key']; $sign = $_GET['sign']; $mem = new \Memcache(); $mem->connect('127.0.0.1',11211); $data = json_decode($mem->get($key),true);//取出memcache的值 if($data['sign']!=$sign){//驗證傳遞的sign $return = array('status'=>0,'msg'=>'驗證錯誤'); return $this->createJsonResponse($return); }else{ if($login){//手機掃碼網頁登錄,把用戶名存進memcache $data['login'] = $login; $res = json_encode($data); $mem->set($key,$res,0,180); $return = array('status'=>1,'msg'=>'登陸成功'); return $this->createJsonResponse($return); }else{ $return = array('status'=>0,'msg'=>'請傳遞正確的用戶信息'); return $this->createJsonResponse($return); } } }
四、確認登陸
走到這裏,就到了本次掃碼登陸的最後一步了,也是最關鍵的一步,不知道諸位看官們把symfony2的security安全登陸機制看的怎麼樣了,原理先無論了,畢竟不在本次的討論範圍以內,直接說改哪好了。
/src/topxia/WebBundle/Handler/AuthenticationProvider.php 的checkAuthentication方法,能夠直接改,也能夠繼承到custom再改。
php代碼以下:
五、二維碼過時設置
爲了安全考慮,設置二維碼過時是很關鍵的一個步驟,在全部的php代碼裏,存放在memcache中的數據都有一個時間限制,本示例中的時間是3分鐘,過時後,memcache會刪除掉原有的數據記錄,當ajax請求不到數據的時候,要在頁面顯示二維碼已過時,要求從新刷新二維碼。
效果圖以下:
結論:
一、php掃碼登陸只是一個確認的過程,在每次訪問接口的時候,安全驗證尤其重要,本次方法未涉及到驗證的算法,請諸位根據自身項目進行補充調整,先有理念再說嘛。
二、對於ajax輪詢的方法是否low,嗯,low。還有更好的實現方式,好比websocket,goeasy等你們見仁見智,不過貌似支付寶和京東的掃碼都是輪詢,不對請見諒。
三、本次掃碼登陸只是一個理念,不只僅針對edusoho平臺,全部的均可以移植過去使用,不過,作好安全就好。
四、前端二維碼框的html和css代碼諸位不會找我要了吧,畢竟大家都是大牛嘛。
五、能看到這裏,真的挺感謝,沒白寫一場,另外,大牛們打擊的時候手下留情些,我還有第二版呢,也許比這個好哦。
原文地址:http://www.cnblogs.com/spareribs/p/7093821.html