中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PHP掃碼登錄原理及實現方法有哪些

發布時間:2020-07-22 13:52:34 來源:億速云 閱讀:434 作者:Leah 欄目:編程語言

這期內容當中小編將會給大家帶來有關PHP掃碼登錄原理及實現方法有哪些,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

掃碼登錄的原理

整體流程

為方便理解,我簡單畫了一個 UML 時序圖,用以描述掃碼登錄的大致流程!

總結下核心流程:

  1. 請求業務服務器獲取用以登錄的二維碼和 UUID。

  2. 通過 websocket 連接 socket 服務器,并定時(時間間隔依據服務器配置時間調整)發送心跳保持連接。

  3. 用戶通過 APP 掃描二維碼,發送請求到業務服務器處理登錄。根據 UUID 設置登錄結果。

  4. socket 服務器通過監聽獲取登錄結果,建立 session 數據,根據 UUID 推送登錄數據到用戶瀏覽器。

  5. 用戶登錄成功,服務器主動將該 socker 連接從連接池中剔除,該二維碼失效。

關于客戶端標識

也就是 UUID,這是貫穿整個流程的紐帶,一個閉環登錄過程,每一步業務處理都是圍繞該次的 UUD 進行處理的。UUID 的生成有根據 session_id 的也有根據客戶端 ip 地址的。個人還是建議每個二維碼都有單獨的 UUID,適用場景更廣一些!

關于前端和服務器通訊

前端肯定是要和服務器保持一直通訊的,用以獲取登錄結果和二維碼狀態。看了下網上的一些實現方案,基本各個方案都有用的:輪詢、長輪詢、長鏈接、websocket。也不能肯定的說哪個方案好哪個方案不好,只能說哪個方案更適用于當前應用場景。個人比較建議使用長輪詢、websocket 這種比較節省服務器性能的方案。

關于安全性

掃碼登錄的好處顯而易見,一是人性化,再就是防止密碼泄漏。但是新方式的接入,往往也伴隨著新的風險。所以,很有必要再整體過程中加入適當的安全機制。例如:

  • 強制 HTTPS 協議
  • 短期令牌
  • 數據簽名
  • 數據加密

掃碼登錄的過程演示

代碼實現和源碼后面會給出。

開啟 Socket 服務器

訪問登錄頁面

可以看到用戶請求的二維碼資源,并獲取到了 qid

獲取二維碼時候會建立相應緩存,并設置過期時間:

之后會連接 socket 服務器,定時發送心跳。

此時 socket 服務器會有相應連接日志輸出:

用戶使用 APP 掃碼并授權

服務器驗證并處理登錄,創建 session,建立對應的緩存:

Socket 服務器讀取到緩存,開始推送信息,并關閉剔除連接:

前端獲取信息,處理登錄:

掃碼登錄的實現

注意:本 Demo 只是個人學習測試,所以并未做太多安全機制!

Socket 代理服務器

使用 Nginx 作為代理 socke 服務器。可使用域名,方便做負載均衡。本次測試域名:loc.websocket.net

websocker.conf

server {
    listen       80;
    server_name  loc.websocket.net;
    root   /www/websocket;
    index  index.php index.html index.htm;
    #charset koi8-r;

    access_log /dev/null;
    #access_log  /var/log/nginx/nginx.localhost.access.log  main;
    error_log  /var/log/nginx/nginx.websocket.error.log  warn;

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    location / {
        proxy_pass http://php-cli:8095/;
        proxy_http_version 1.1;
        proxy_connect_timeout 4s;
        proxy_read_timeout 60s;
        proxy_send_timeout 12s;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

Socket 服務器

使用 PHP 構建的 socket 服務器。實際項目中大家可以考慮使用第三方應用,穩定性更好一些!

QRServer.php

<?php

require_once dirname(dirname(__FILE__)) . '/Config.php';
require_once dirname(dirname(__FILE__)) . '/lib/RedisUtile.php';
require_once dirname(dirname(__FILE__)) . '/lib/Common.php';/**
 * 掃碼登陸服務端
 * Class QRServer
 * @author BNDong */class QRServer {    private $_sock;    private $_redis;    private $_clients = array();    /**
     * socketServer constructor.     */
    public function __construct()
    {        // 設置 timeout
        set_time_limit(0);        // 創建一個套接字(通訊節點)
        $this->_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create socket" . PHP_EOL);
        socket_set_option($this->_sock, SOL_SOCKET, SO_REUSEADDR, 1);        // 綁定地址
        socket_bind($this->_sock, \Config::QRSERVER_HOST, \Config::QRSERVER_PROT) or die("Could not bind to socket" . PHP_EOL);        // 監聽套接字上的連接
        socket_listen($this->_sock, 4) or die("Could not set up socket listener" . PHP_EOL);

        $this->_redis  = \lib\RedisUtile::getInstance();
    }    /**
     * 啟動服務     */
    public function run()
    {
        $this->_clients = array();
        $this->_clients[uniqid()] = $this->_sock;        while (true){
            $changes = $this->_clients;
            $write   = NULL;
            $except  = NULL;
            socket_select($changes,  $write,  $except, NULL);            foreach ($changes as $key => $_sock) {                if($this->_sock == $_sock){ // 判斷是不是新接入的 socket

                    if(($newClient = socket_accept($_sock))  === false){
                        die('failed to accept socket: '.socket_strerror($_sock)."\n");
                    }

                    $buffer   = trim(socket_read($newClient, 1024)); // 讀取請求
                    $response = $this->handShake($buffer);
                    socket_write($newClient, $response, strlen($response)); // 發送響應
                    socket_getpeername($newClient, $ip); // 獲取 ip 地址
                    $qid = $this->getHandQid($buffer);
                    $this->log("new clinet: ". $qid);                    if ($qid) { // 驗證是否存在 qid
                        if (isset($this->_clients[$qid])) $this->close($qid, $this->_clients[$qid]);
                        $this->_clients[$qid] = $newClient;
                    } else {
                        $this->close($qid, $newClient);
                    }

                } else {                    // 判斷二維碼是否過期
                    if ($this->_redis->exists(\lib\Common::getQidKey($key))) {

                        $loginKey = \lib\Common::getQidLoginKey($key);                        if ($this->_redis->exists($loginKey)) { // 判斷用戶是否掃碼
                            $this->send($key, $this->_redis->get($loginKey));
                            $this->close($key, $_sock);
                        }

                        $res = socket_recv($_sock, $buffer,  2048, 0);                        if (false === $res) {
                            $this->close($key, $_sock);
                        } else {
                            $res && $this->log("{$key} clinet msg: " . $this->message($buffer));
                        }
                    } else {
                        $this->close($key, $this->_clients[$key]);
                    }

                }
            }
            sleep(1);
        }
    }    /**
     * 構建響應
     * @param string $buf
     * @return string     */
    private function handShake($buf){
        $buf    = substr($buf,strpos($buf,'Sec-WebSocket-Key:') + 18);
        $key    = trim(substr($buf, 0, strpos($buf,"\r\n")));
        $newKey = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
        $newMessage = "HTTP/1.1 101 Switching Protocols\r\n";
        $newMessage .= "Upgrade: websocket\r\n";
        $newMessage .= "Sec-WebSocket-Version: 13\r\n";
        $newMessage .= "Connection: Upgrade\r\n";
        $newMessage .= "Sec-WebSocket-Accept: " . $newKey . "\r\n\r\n";        return $newMessage;
    }    /**
     * 獲取 qid
     * @param string $buf
     * @return mixed|string     */
    private function getHandQid($buf) {
        preg_match("/^[\s\n]?GET\s+\/\?qid\=([a-z0-9]+)\s+HTTP.*/", $buf, $matches);
        $qid = isset($matches[1]) ? $matches[1] : '';        return $qid;
    }    /**
     * 編譯發送數據
     * @param string $s
     * @return string     */
    private function frame($s) {
        $a = str_split($s, 125);        if (count($a) == 1) {            return "\x81" . chr(strlen($a[0])) . $a[0];
        }
        $ns = "";        foreach ($a as $o) {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }        return $ns;
    }    /**
     * 解析接收數據
     * @param resource $buffer
     * @return null|string     */
    private function message($buffer){
        $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;        if ($len === 126)  {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127)  {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else  {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }        return $decoded;
    }    /**
     * 發送消息
     * @param string $qid
     * @param string $msg     */
    private function send($qid, $msg)
    {
        $frameMsg = $this->frame($msg);
        socket_write($this->_clients[$qid], $frameMsg, strlen($frameMsg));
        $this->log("{$qid} clinet send: " . $msg);
    }    /**
     * 關閉 socket
     * @param string $qid
     * @param resource $socket     */
    private function close($qid, $socket)
    {
        socket_close($socket);        if (array_key_exists($qid, $this->_clients)) unset($this->_clients[$qid]);
        $this->_redis->del(\lib\Common::getQidKey($qid));
        $this->_redis->del(\lib\Common::getQidLoginKey($qid));
        $this->log("{$qid} clinet close");
    }    /**
     * 日志記錄
     * @param string $msg     */
    private function log($msg)
    {
        echo '['. date('Y-m-d H:i:s') .'] ' . $msg . "\n";
    }
}

$server = new QRServer();
$server->run();

登錄頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>掃碼登錄 - 測試頁面</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="./public/css/main.css">
</head>
<body translate="no">

<p class='box'>
    <p class='box-form'>
        <p class='box-login-tab'></p>
        <p class='box-login-title'>
            <p class='i i-login'></p><h3>登錄</h3>
        </p>
        <p class='box-login'>
            <p class='fieldset-body' id='login_form'>
                <button onclick="openLoginInfo();" class='b b-form i i-more' title='Mais Informa??es'></button>
                <p class='field'>
                    <label for='user'>用戶賬戶</label>
                    <input type='text' id='user' name='user' title='Username' placeholder="請輸入用戶賬戶/郵箱地址" />
                </p>
                <p class='field'>
                    <label for='pass'>用戶密碼</label>
                    <input type='password' id='pass' name='pass' title='Password' placeholder="情輸入賬戶密碼" />
                </p>
                <label class='checkbox'>
                    <input type='checkbox' value='TRUE' title='Keep me Signed in' /> 記住我                </label>
                <input type='submit' id='do_login' value='登錄' title='登錄' />
            </p>
        </p>
    </p>
    <p class='box-info'>
        <p><button onclick="closeLoginInfo();" class='b b-info i i-left' title='Back to Sign In'></button><h4>掃碼登錄</h4>
        </p>
        <p class='line-wh'></p>
        <p style="position: relative;">
            <input type="hidden" id="qid" value="">
            <p id="qrcode-exp">二維碼已失效<br>點擊重新獲取</p>
            <img id="qrcode" src="" />
        </p>
    </p>
</p>
<script src='./public/js/jquery.min.js'></script>
<script src='./public/js/modernizr.min.js'></script>
<script id="rendered-js">
    $(document).ready(function () {

        restQRCode();
        openLoginInfo();
        $('#qrcode-exp').click(function () {
            restQRCode();
            $(this).hide();
        });
    });    /**
     * 打開二維碼     */
    function openLoginInfo() {
        $(document).ready(function () {
            $('.b-form').css("opacity", "0.01");
            $('.box-form').css("left", "-100px");
            $('.box-info').css("right", "-100px");
        });
    }    /**
     * 關閉二維碼     */
    function closeLoginInfo() {
        $(document).ready(function () {
            $('.b-form').css("opacity", "1");
            $('.box-form').css("left", "0px");
            $('.box-info').css("right", "-5px");
        });
    }    /**
     * 刷新二維碼     */
    var ws, wsTid = null;
    function restQRCode() {

        $.ajax({
            url: 'http://localhost/qrcode/code.php',
            type:'post',
            dataType: "json",            async: false,
            success:function (result) {
                $('#qrcode').attr('src', result.img);
                $('#qid').val(result.qid);
            }
        });        if ("WebSocket" in window) {            if (typeof ws != 'undefined'){
                ws.close();                null != wsTid && window.clearInterval(wsTid);
            }

            ws = new WebSocket("ws://loc.websocket.net?qid=" + $('#qid').val());

            ws.onopen = function() {
                console.log('websocket 已連接上!');
            };

            ws.onmessage = function(e) {                // todo: 本函數做登錄處理,登錄判斷,創建緩存信息!                console.log(e.data);                var result = JSON.parse(e.data);
                console.log(result);
                alert('登錄成功:' + result.name);
            };

            ws.onclose = function() {
                console.log('websocket 連接已關閉!');
                $('#qrcode-exp').show();                null != wsTid && window.clearInterval(wsTid);
            };            // 發送心跳
            wsTid = window.setInterval( function () {                if (typeof ws != 'undefined') ws.send('1');
            }, 50000 );

        } else {            // todo: 不支持 WebSocket 的,可以使用 js 輪詢處理,這里不作該功能實現!
            alert('您的瀏覽器不支持 WebSocket!');
        }
    }</script>
</body>
</html>

登錄處理

測試使用,模擬登錄處理,未做安全認證!!

<?php

require_once dirname(__FILE__) . '/lib/RedisUtile.php';
require_once dirname(__FILE__) . '/lib/Common.php';/**
 * -------  登錄邏輯模擬 --------
 * 請根據實際編寫登錄邏輯并處理安全驗證 */$qid = $_GET['qid'];
$uid = $_GET['uid'];

$data = array();switch ($uid)
{    case '1':
        $data['uid']  = 1;
        $data['name'] = '張三';        break;    case '2':
        $data['uid']  = 2;
        $data['name'] = '李四';        break;
}

$data  = json_encode($data);
$redis = \lib\RedisUtile::getInstance();
$redis->setex(\lib\Common::getQidLoginKey($qid), 1800, $data);

上述就是小編為大家分享的PHP掃碼登錄原理及實現方法有哪些了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

php
AI

丹阳市| 龙里县| 沂源县| 古田县| 龙州县| 安化县| 三河市| 叶城县| 高要市| 黑河市| 习水县| 涞源县| 平阴县| 隆昌县| 阿克苏市| 普兰店市| 电白县| 观塘区| 民和| 沅江市| 视频| 武强县| 屏东县| 板桥市| 大同县| 高台县| 张家界市| 昭觉县| 儋州市| 齐河县| 密山市| 冀州市| 霍城县| 宜宾市| 吴桥县| 玉林市| 梨树县| 呈贡县| 平泉县| 新邵县| 天全县|