Table of Contents
Principle of QR code login
Overall process
About the client identification
About front-end and server communication
About security
Scan code login process demonstration
Open the Socket server
Visit the login page
The user uses the APP to scan the code and authorize
Implementation of scanning QR code login
Socket proxy server
Socket server
Login page
Login processing
Home Backend Development PHP Tutorial Share the PHP code scanning login principle and implementation method

Share the PHP code scanning login principle and implementation method

Jul 09, 2020 pm 02:07 PM
php

Since QR code login is more convenient, faster and more flexible than account and password login, it is more popular among users in actual use.

This article mainly introduces the principle and overall process of scanning QR codes to log in, including the generation/obtaining of QR codes, expiration and invalidation processing, and monitoring of login status.

Principle of QR code login

Overall process

To facilitate understanding, I simply drew a UML sequence diagram to describe the general process of QR code login!

Summarize the core process:

  1. Request the business server to obtain the QR code and UUID for login.

  2. Connect to the socket server through websocket, and send heartbeats regularly (the time interval is adjusted according to the server configuration time) to maintain the connection.

  3. The user scans the QR code through the APP and sends a request to the business server to process the login. Set login results based on UUID.

  4. The socket server obtains the login result through monitoring, establishes session data, and pushes the login data to the user's browser based on the UUID.

  5. The user logged in successfully, the server actively removed the socker connection from the connection pool, and the QR code became invalid.

About the client identification

It is also the UUID. This is the link throughout the entire process. It is a closed-loop login process. Each step of business processing is based on the UUD. processed. UUID is generated based on session_id or client IP address. Personally, I still recommend that each QR code has a separate UUID, which is applicable to a wider range of scenarios!

About front-end and server communication

The front-end must maintain constant communication with the server to obtain the login result and QR code status. After looking at some implementation solutions on the Internet, basically all solutions are useful: polling, long polling, long links, and websockets. We cannot definitely say which solution is better and which one is worse. We can only say which solution is more suitable for the current application scenario. Personally, I recommend using long polling and websocket, which save server performance.

About security

The benefits of scanning the QR code to log in are obvious, one is humanization, and the other is to prevent password leakage. However, new methods of access are often accompanied by new risks. Therefore, it is necessary to add appropriate safety mechanisms into the overall process. For example:

  • Force HTTPS protocol
  • Short-lived token
  • Data signature
  • Data encryption

Scan code login process demonstration

Code implementation and source code will be given later.

Open the Socket server

Visit the login page

You can see the QR code resource requested by the user and obtain the qid.

When obtaining the QR code, the corresponding cache will be established and the expiration time will be set:

The socket server will be connected and heartbeats will be sent regularly.

At this time, the socket server will have corresponding connection log output:

The user uses the APP to scan the code and authorize

The server verifies and processes the login, creates the session, and establishes the corresponding cache:

The Socket server reads the cache, starts pushing information, and closes the elimination connection:

The front end obtains information and processes login:

Implementation of scanning QR code login

Note: This Demo is just a personal learning test, so it does not have many security mechanisms!

Socket proxy server

Use Nginx as the proxy socke server. Domain names can be used to facilitate load balancing. The domain name of this test: 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;
    }
}
Copy after login

Socket server

is built using PHP socket server. In actual projects, you can consider using third-party applications, which will provide better stability!

QRServer.php

<?php

require_once dirname(dirname(__FILE__)) . &#39;/Config.php&#39;;
require_once dirname(dirname(__FILE__)) . &#39;/lib/RedisUtile.php&#39;;
require_once dirname(dirname(__FILE__)) . &#39;/lib/Common.php&#39;;/**
 * 扫码登陆服务端
 * 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(&#39;failed to accept socket: &#39;.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,&#39;Sec-WebSocket-Key:&#39;) + 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] : &#39;&#39;;        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 &#39;[&#39;. date(&#39;Y-m-d H:i:s&#39;) .&#39;] &#39; . $msg . "\n";
    }
}

$server = new QRServer();
$server->run();
Copy after login

Login page

<!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=&#39;box&#39;>
    <p class=&#39;box-form&#39;>
        <p class=&#39;box-login-tab&#39;></p>
        <p class=&#39;box-login-title&#39;>
            <p class=&#39;i i-login&#39;></p><h2>登录</h2>
        </p>
        <p class=&#39;box-login&#39;>
            <p class=&#39;fieldset-body&#39; id=&#39;login_form&#39;>
                <button onclick="openLoginInfo();" class=&#39;b b-form i i-more&#39; title=&#39;Mais Informações&#39;></button>
                <p class=&#39;field&#39;>
                    <label for=&#39;user&#39;>用户账户</label>
                    <input type=&#39;text&#39; id=&#39;user&#39; name=&#39;user&#39; title=&#39;Username&#39; placeholder="请输入用户账户/邮箱地址" />
                </p>
                <p class=&#39;field&#39;>
                    <label for=&#39;pass&#39;>用户密码</label>
                    <input type=&#39;password&#39; id=&#39;pass&#39; name=&#39;pass&#39; title=&#39;Password&#39; placeholder="情输入账户密码" />
                </p>
                <label class=&#39;checkbox&#39;>
                    <input type=&#39;checkbox&#39; value=&#39;TRUE&#39; title=&#39;Keep me Signed in&#39; /> 记住我                </label>
                <input type=&#39;submit&#39; id=&#39;do_login&#39; value=&#39;登录&#39; title=&#39;登录&#39; />
            </p>
        </p>
    </p>
    <p class=&#39;box-info&#39;>
        <p><button onclick="closeLoginInfo();" class=&#39;b b-info i i-left&#39; title=&#39;Back to Sign In&#39;></button><h3>扫码登录</h3>
        </p>
        <p class=&#39;line-wh&#39;></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=&#39;./public/js/jquery.min.js&#39;></script>
<script src=&#39;./public/js/modernizr.min.js&#39;></script>
<script id="rendered-js">
    $(document).ready(function () {

        restQRCode();
        openLoginInfo();
        $(&#39;#qrcode-exp&#39;).click(function () {
            restQRCode();
            $(this).hide();
        });
    });    /**
     * 打开二维码     */
    function openLoginInfo() {
        $(document).ready(function () {
            $(&#39;.b-form&#39;).css("opacity", "0.01");
            $(&#39;.box-form&#39;).css("left", "-100px");
            $(&#39;.box-info&#39;).css("right", "-100px");
        });
    }    /**
     * 关闭二维码     */
    function closeLoginInfo() {
        $(document).ready(function () {
            $(&#39;.b-form&#39;).css("opacity", "1");
            $(&#39;.box-form&#39;).css("left", "0px");
            $(&#39;.box-info&#39;).css("right", "-5px");
        });
    }    /**
     * 刷新二维码     */
    var ws, wsTid = null;
    function restQRCode() {

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

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

            ws.onopen = function() {
                console.log(&#39;websocket 已连接上!&#39;);
            };

            ws.onmessage = function(e) {                // todo: 本函数做登录处理,登录判断,创建缓存信息!                console.log(e.data);                var result = JSON.parse(e.data);
                console.log(result);
                alert(&#39;登录成功:&#39; + result.name);
            };

            ws.onclose = function() {
                console.log(&#39;websocket 连接已关闭!&#39;);
                $(&#39;#qrcode-exp&#39;).show();                null != wsTid && window.clearInterval(wsTid);
            };            // 发送心跳
            wsTid = window.setInterval( function () {                if (typeof ws != &#39;undefined&#39;) ws.send(&#39;1&#39;);
            }, 50000 );

        } else {            // todo: 不支持 WebSocket 的,可以使用 js 轮询处理,这里不作该功能实现!
            alert(&#39;您的浏览器不支持 WebSocket!&#39;);
        }
    }</script>
</body>
</html>
Copy after login

Login processing

Test use, simulated login processing, no security authentication ! !

<?php

require_once dirname(__FILE__) . &#39;/lib/RedisUtile.php&#39;;
require_once dirname(__FILE__) . &#39;/lib/Common.php&#39;;/**
 * -------  登录逻辑模拟 --------
 * 请根据实际编写登录逻辑并处理安全验证 */$qid = $_GET[&#39;qid&#39;];
$uid = $_GET[&#39;uid&#39;];

$data = array();switch ($uid)
{    case &#39;1&#39;:
        $data[&#39;uid&#39;]  = 1;
        $data[&#39;name&#39;] = &#39;张三&#39;;        break;    case &#39;2&#39;:
        $data[&#39;uid&#39;]  = 2;
        $data[&#39;name&#39;] = &#39;李四&#39;;        break;
}

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

For more related knowledge, please visit PHP Chinese website!

The above is the detailed content of Share the PHP code scanning login principle and implementation method. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian Dec 24, 2024 pm 04:42 PM

PHP 8.4 brings several new features, security improvements, and performance improvements with healthy amounts of feature deprecations and removals. This guide explains how to install PHP 8.4 or upgrade to PHP 8.4 on Ubuntu, Debian, or their derivati

7 PHP Functions I Regret I Didn't Know Before 7 PHP Functions I Regret I Didn't Know Before Nov 13, 2024 am 09:42 AM

If you are an experienced PHP developer, you might have the feeling that you’ve been there and done that already.You have developed a significant number of applications, debugged millions of lines of code, and tweaked a bunch of scripts to achieve op

How To Set Up Visual Studio Code (VS Code) for PHP Development How To Set Up Visual Studio Code (VS Code) for PHP Development Dec 20, 2024 am 11:31 AM

Visual Studio Code, also known as VS Code, is a free source code editor — or integrated development environment (IDE) — available for all major operating systems. With a large collection of extensions for many programming languages, VS Code can be c

Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Apr 05, 2025 am 12:04 AM

JWT is an open standard based on JSON, used to securely transmit information between parties, mainly for identity authentication and information exchange. 1. JWT consists of three parts: Header, Payload and Signature. 2. The working principle of JWT includes three steps: generating JWT, verifying JWT and parsing Payload. 3. When using JWT for authentication in PHP, JWT can be generated and verified, and user role and permission information can be included in advanced usage. 4. Common errors include signature verification failure, token expiration, and payload oversized. Debugging skills include using debugging tools and logging. 5. Performance optimization and best practices include using appropriate signature algorithms, setting validity periods reasonably,

How do you parse and process HTML/XML in PHP? How do you parse and process HTML/XML in PHP? Feb 07, 2025 am 11:57 AM

This tutorial demonstrates how to efficiently process XML documents using PHP. XML (eXtensible Markup Language) is a versatile text-based markup language designed for both human readability and machine parsing. It's commonly used for data storage an

PHP Program to Count Vowels in a String PHP Program to Count Vowels in a String Feb 07, 2025 pm 12:12 PM

A string is a sequence of characters, including letters, numbers, and symbols. This tutorial will learn how to calculate the number of vowels in a given string in PHP using different methods. The vowels in English are a, e, i, o, u, and they can be uppercase or lowercase. What is a vowel? Vowels are alphabetic characters that represent a specific pronunciation. There are five vowels in English, including uppercase and lowercase: a, e, i, o, u Example 1 Input: String = "Tutorialspoint" Output: 6 explain The vowels in the string "Tutorialspoint" are u, o, i, a, o, i. There are 6 yuan in total

Explain late static binding in PHP (static::). Explain late static binding in PHP (static::). Apr 03, 2025 am 12:04 AM

Static binding (static::) implements late static binding (LSB) in PHP, allowing calling classes to be referenced in static contexts rather than defining classes. 1) The parsing process is performed at runtime, 2) Look up the call class in the inheritance relationship, 3) It may bring performance overhead.

What are PHP magic methods (__construct, __destruct, __call, __get, __set, etc.) and provide use cases? What are PHP magic methods (__construct, __destruct, __call, __get, __set, etc.) and provide use cases? Apr 03, 2025 am 12:03 AM

What are the magic methods of PHP? PHP's magic methods include: 1.\_\_construct, used to initialize objects; 2.\_\_destruct, used to clean up resources; 3.\_\_call, handle non-existent method calls; 4.\_\_get, implement dynamic attribute access; 5.\_\_set, implement dynamic attribute settings. These methods are automatically called in certain situations, improving code flexibility and efficiency.

See all articles