栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > PHP

分享PHP扫码登录原理及实现方法

PHP 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

分享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 {
    listen80;
    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

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

  }
     }
     sleep(1);
 }
    }    
    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;
    }    
    private function getHandQid($buf) {
 preg_match("/^[sn]?GETs+/?qid=([a-z0-9]+)s+HTTP.*/", $buf, $matches);
 $qid = isset($matches[1]) ? $matches[1] : ''; return $qid;
    }    
    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;
    }    
    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;
    }    
    private function send($qid, $msg)
    {
 $frameMsg = $this->frame($msg);
 socket_write($this->_clients[$qid], $frameMsg, strlen($frameMsg));
 $this->log("{$qid} clinet send: " . $msg);
    }    
    private function close($qid, $socket)
    {
 socket_close($socket); if (array_key_exists($qid, $this->_clients)) unset($this->_clients[$qid]);
 $this->_redis->del(libCommon::getQidKey($qid));
 $this->_redis->del(libCommon::getQidLoginKey($qid));
 $this->log("{$qid} clinet close");
    }    
    private function log($msg)
    {
 echo '['. date('Y-m-d H:i:s') .'] ' . $msg . "n";
    }
}

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

登录页面



    
    扫码登录 - 测试页面
    
    



登录

扫码登录

二维码已失效
点击重新获取

登录处理

测试使用,模拟登录处理,未做安全认证!!

setex(libCommon::getQidLoginKey($qid), 1800, $data);

更多相关知识,请访问考高分网!

以上就是分享PHP扫码登录原理及实现方法的详细内容,更多请关注考高分网其它相关文章!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/262940.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号