加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

详解PHP扫码登录原理及实现方法

发布时间:2022-07-07 15:27:53 所属栏目:PHP教程 来源:互联网
导读:由于扫码登录比账号密码登录更方便、快捷、灵活,在实际使用中更受到用户的欢迎。 本文主要介绍了扫码登录的原理及整体流程,包含了二维码的生成/获
  由于扫码登录比账号密码登录更方便、快捷、灵活,在实际使用中更受到用户的欢迎。
 
  本文主要介绍了扫码登录的原理及整体流程,包含了二维码的生成/获取、过期失效的处理、登录状态的监听。
 
  扫码登录的原理
 
  整体流程
 
  为方便理解,我简单画了一个 UML 时序图,用以描述扫码登录的大致流程!
 
  总结下核心流程:
 
  请求业务服务器获取用以登录的二维码和 UUID。
 
  通过 websocket 连接 socket 服务器,并定时(时间间隔依据服务器配置时间调整)发送心跳保持连接。
 
  用户通过 APP 扫描二维码,发送请求到业务服务器处理登录。根据 UUID 设置登录结果。
 
  socket 服务器通过监听获取登录结果,建立 session 数据,根据 UUID 推送登录数据到用户浏览器。
 
  用户登录成功,服务器主动将该 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 {
   
    
          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  = libRedisUtile::getInstance();
   
      }    /**
   
       * 启动服务     */
               $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) { //  
   
   
                  } else {                    // 判断二维码是否过期
   
                      if ($this->_redis->exists(libCommon::getQidKey($key))) {
   
   
   
                          $loginKey = libCommon::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]);
   
                      }
   
        * @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,"rn")));
   
          $newKey = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
   
          $newMessage = "HTTP/1.1 101 Switching Protocolsrn";
   
          $newMessage .= "Upgrade: websocketrn";
   
          $newMessage .= "Sec-WebSocket-Version: 13rn";
   
          $newMessage .= "Connection: Upgradern";
   
          $newMessage .= "Sec-WebSocket-Accept: " . $newKey . "rnrn";        return $newMessage;
   
      }    /**
   
           preg_match("/^[sn]?GETs+/?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;
   
      }    /** 

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读