1. 程式人生 > >初涉掃碼登錄:edusoho實現客戶端掃碼登錄(簡版)

初涉掃碼登錄:edusoho實現客戶端掃碼登錄(簡版)

confirm 鍵值 版本號 error == form 通過 遮罩層 strrev

一、項目簡介及需求

edusoho是一套商業版的在線教育平臺,項目本身基於symfony2框架開發,現在有一款自己的APP,要求在不多修改edusoho自身代碼的基礎上,實現客戶端對PC端掃碼登錄。不多修改edusoho代碼的原因是為了避免下次升級主版本時發生錯誤。

二、版本信息及所需應用

edusoho 7.5.14

php   5.5.25

php   GD庫

memcache(本次使用memcache作為存儲介質,可用redis代替) 點我下載

phpqrcde(php生成二維碼插件)

三、實現思路

點擊頁面掃碼按鈕,向php發送一個請求,php生成二維碼並把sign驗證字符串保存至memcache中,請求成功後,頁面展示二維碼等待掃碼,並使用ajax以輪詢的方式請求後臺方法,查詢是否已掃碼。手機掃碼後訪問二維碼裏面的url鏈接,php保存已掃碼標識,並向ajax返回已掃碼狀態,前臺頁面提示已掃碼,並再次發送是否已確認登錄的ajax輪詢。掃碼完成後,APP端顯示是否登錄頁面,點擊確認,向後臺方法傳遞用戶名及sign驗證字符串,後臺驗證通過後,返回用戶名,sign及狀態碼至前端ajax。

由於需求是不多改動edusoho自身代碼,所以登錄時借用edusoho本身的登錄,即把ajax返回的用戶名填寫進用戶名框,sign填進密碼框,觸發登錄的submit按鈕,只在登錄的密碼驗證處添加了幾行代碼,把密碼驗證,改為了sign驗證。(symfony2采用的登錄方式是security配置化登錄,後面會講到在哪裏改動密碼驗證,不熟悉的可以參考 security安全登錄)

四、實現代碼及步驟

1、生成登錄二維碼

(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);
    }

2、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);
    }

3、掃碼成功後判斷是否確認登錄

(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);
            }
        }

    }

4、確認登錄

走到這裏,就到了本次掃碼登錄的最後一步了,也是最關鍵的一步,不知道諸位看官們把symfony2的security安全登錄機制看的怎麽樣了,原理先不管了,畢竟不在本次的討論範圍之內,直接說改哪好了。

/src/topxia/WebBundle/Handler/AuthenticationProvider.php 的checkAuthentication方法,可以直接改,也可以繼承到custom再改。

php代碼如下:

技術分享

5、二維碼過期設置

為了安全考慮,設置二維碼過期是很關鍵的一個步驟,在所有的php代碼裏,存放在memcache中的數據都有一個時間限制,本示例中的時間是3分鐘,過期後,memcache會刪除掉原有的數據記錄,當ajax請求不到數據的時候,要在頁面顯示二維碼已過期,要求重新刷新二維碼。

效果圖如下:

技術分享

結論:

1、php掃碼登錄只是一個確認的過程,在每次訪問接口的時候,安全驗證尤為重要,本次方法未涉及到驗證的算法,請諸位根據自身項目進行補充調整,先有理念再說嘛。

2、對於ajax輪詢的方法是否low,嗯,low。還有更好的實現方式,比如websocket,goeasy等大家見仁見智,不過貌似支付寶和京東的掃碼都是輪詢,不對請見諒。

3、本次掃碼登錄只是一個理念,不僅僅針對edusoho平臺,所有的都可以移植過去使用,不過,做好安全就好。

4、前端二維碼框的html和css代碼諸位不會找我要了吧,畢竟你們都是大牛嘛。

5、能看到這裏,真的挺感謝,沒白寫一場,另外,大牛們打擊的時候手下留情些,我還有第二版呢,也許比這個好哦。

初涉掃碼登錄:edusoho實現客戶端掃碼登錄(簡版)