PHP 코드 스캐닝 로그인 원리 및 구현 방법 공유
QR코드 로그인은 계정 및 비밀번호 로그인보다 더 편리하고 빠르며 유연하기 때문에 실제 사용하는 사용자들 사이에서 더 인기가 높습니다.
본 글에서는 QR코드 생성/획득, 만료 및 무효화 처리, 로그인 상태 모니터링 등 QR코드를 스캔하여 로그인하는 원리와 전반적인 과정을 주로 소개합니다.
QR코드 로그인 원리
전체 과정
이해를 돕기 위해 QR코드 로그인의 일반적인 과정을 간단하게 UML 시퀀스 다이어그램으로 그려보았습니다!
핵심 프로세스 요약:
비즈니스 서버에 로그인을 위한 QR 코드와 UUID를 요청하세요.
웹소켓을 통해 소켓 서버에 접속하고, 정기적으로 하트비트를 보내(시간 간격은 서버 구성 시간에 따라 조정됨) 연결을 유지합니다.
사용자는 앱을 통해 QR 코드를 스캔하고 비즈니스 서버에 로그인 처리 요청을 보냅니다. UUID를 기반으로 로그인 결과를 설정합니다.
소켓 서버는 모니터링을 통해 로그인 결과를 얻어 세션 데이터를 설정하고 UUID를 기반으로 로그인 데이터를 사용자의 브라우저에 푸시합니다.
사용자가 성공적으로 로그인하면 서버가 연결 풀에서 소켓 연결을 적극적으로 제거하고 QR 코드가 유효하지 않게 됩니다.
클라이언트 식별 정보
(UUID라고도 함)는 전체 프로세스, 폐쇄 루프 로그인 프로세스 및 비즈니스 처리의 각 단계에 대한 링크인 UUD를 중심으로 처리됩니다. UUID는 session_id 또는 클라이언트 IP 주소를 기반으로 생성됩니다. 개인적으로 저는 여전히 각 QR 코드에 별도의 UUID가 있으므로 더 넓은 범위의 시나리오에 적용할 수 있는 것을 권장합니다!
프런트엔드 및 서버 통신에 대하여
프런트엔드는 로그인 결과 및 QR 코드 상태를 얻기 위해 서버와 지속적인 통신을 유지해야 합니다. 인터넷에서 일부 구현 솔루션을 살펴본 후 기본적으로 폴링, 롱 폴링, 롱 링크 및 웹소켓과 같은 모든 솔루션이 유용합니다. 어떤 솔루션이 더 좋고 어떤 솔루션이 더 나쁘다고 확실히 말할 수는 없습니다. 현재 애플리케이션 시나리오에 어떤 솔루션이 더 적합한지는 말할 수 있습니다. 개인적으로는 서버 성능을 절약할 수 있는 롱 폴링과 웹소켓을 사용하는 것을 추천합니다.
보안에 대하여
QR 코드를 스캔하여 로그인하면 얻을 수 있는 이점은 명백합니다. 하나는 인간화이고, 다른 하나는 비밀번호 유출을 방지하는 것입니다. 그러나 새로운 접근 방법에는 새로운 위험이 수반되는 경우가 많습니다. 따라서 전체 프로세스에 적절한 안전 메커니즘을 추가하는 것이 필요합니다. 예:
- 필수 HTTPS 프로토콜
- 단기 토큰
- 데이터 서명
- 데이터 암호화
스캔 코드 로그인 프로세스 데모
코드 구현 및 소스 코드는 나중에 제공됩니다. .
소켓 서버 열기
로그인 페이지 방문
사용자가 요청한 QR 코드 리소스를 확인하고 qid
를 얻을 수 있습니다. qid
。
获取二维码时候会建立相应缓存,并设置过期时间:
之后会连接 socket 服务器,定时发送心跳。
此时 socket 服务器会有相应连接日志输出:
用户使用 APP 扫码并授权
服务器验证并处理登录,创建 session,建立对应的缓存:
Socket 服务器读取到缓存,开始推送信息,并关闭剔除连接:
前端获取信息,处理登录:
扫码登录的实现
注意:本 Demo 只是个人学习测试,所以并未做太多安全机制!
Socket 代理服务器
使用 Nginx 作为代理 socke 服务器。可使用域名,方便做负载均衡。本次测试域名:loc.websocket.net
그런 다음 소켓 서버에 연결되어 정기적으로 하트비트를 보냅니다.
이제 소켓 서버에는 해당 연결 로그 출력이 있습니다.사용자는 APP를 사용하여 코드를 스캔하고 승인합니다
서버는 로그인을 확인하고 처리합니다. 세션 생성 및 통신 설정 캐시:
소켓 서버는 캐시를 읽고 정보 푸시를 시작하며 제거 연결을 닫습니다. 프런트 엔드는 정보를 획득하고 로그인을 처리합니다.
스캔 코드 로그인 구현 참고: 이 데모는 개인 학습 테스트이므로 안전 메커니즘이 많지 않습니다! 소켓 프록시 서버
소켓 프록시 서버
Nginx를 프록시 소켓 서버로 사용하세요. 도메인 이름을 사용하여 로드 밸런싱을 용이하게 할 수 있습니다. 이 테스트의 도메인 이름: loc.websocket.net
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; } }
<?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><h2>登录</h2> </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><h3>扫码登录</h3> </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 코드 스캐닝 로그인 원리 및 구현 방법 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

Session_Start ()로 여러 통화를하면 경고 메시지와 가능한 데이터 덮어 쓰기가 발생합니다. 1) PHP는 세션이 시작되었다는 경고를 발행합니다. 2) 세션 데이터의 예상치 못한 덮어 쓰기를 유발할 수 있습니다. 3) Session_status ()를 사용하여 반복 통화를 피하기 위해 세션 상태를 확인하십시오.

AI는 작곡가 사용을 최적화하는 데 도움이 될 수 있습니다. 특정 방법에는 다음이 포함됩니다. 1. 종속성 관리 최적화 : AI는 종속성을 분석하고 최상의 버전 조합을 권장하며 충돌을 줄입니다. 2. 자동화 된 코드 생성 : AI는 모범 사례를 준수하는 composer.json 파일을 생성합니다. 3. 코드 품질 향상 : AI는 잠재적 인 문제를 감지하고 최적화 제안을 제공하며 코드 품질을 향상시킵니다. 이러한 방법은 기계 학습 및 자연어 처리 기술을 통해 구현되어 개발자가 효율성과 코드 품질을 향상시킬 수 있도록 도와줍니다.

session_start () iscrucialinphpformanagingUsersessions.1) itiniteSanewsessionifnoneexists, 2) ResumesAnxistessions, and3) setSasessionCookieForContInuityAcrosrequests, enablingplicationsirecationSerauthenticationAndpersonalizestContent.

MySQL 기능은 데이터 처리 및 계산에 사용될 수 있습니다. 1. 기본 사용에는 문자열 처리, 날짜 계산 및 수학 연산이 포함됩니다. 2. 고급 사용에는 복잡한 작업을 구현하기 위해 여러 기능을 결합하는 것이 포함됩니다. 3. 성능 최적화를 위해서는 WHERE 절에서 기능 사용 및 GroupBy 및 임시 테이블 사용을 피해야합니다.

HTML5는 5 가지 주요 개선 사항을 제공합니다. 1. 시맨틱 태그는 코드 선명도 및 SEO 효과를 향상시킵니다. 2. 멀티미디어 지원은 비디오 및 오디오 임베딩을 단순화합니다. 3. 형태 향상은 검증을 단순화한다. 4. 오프라인 및 로컬 스토리지는 사용자 경험을 향상시킵니다. 5. 캔버스 및 그래픽 기능은 웹 페이지의 시각화를 향상시킵니다.

Composer는 PHP의 종속성 관리 도구이며 Composer.json 파일을 통해 프로젝트 종속성을 관리합니다. 1) 종속성 정보를 얻기 위해 Composer.json을 구문 분석합니다. 2) 종속성 트리를 형성하기위한 종속성; 3) Packagist에서 공급 업체 디렉토리로 종속성을 다운로드하여 설치합니다. 4) Composer.Lock 파일을 생성하여 팀 일관성 및 프로젝트 유지 관리 가능성을 보장하기 위해 종속성 버전을 잠그십시오.

Typetraits는 컴파일 타임 유형 확인 및 작동에 C에서 사용되어 코드 유연성 및 유형 안전성을 향상시킵니다. 1) 유형 판단은 STD :: IS_INTEGRAL 및 STD :: IS_FLOATING_POINT를 통해 수행되므로 효율적인 유형 확인 및 출력을 달성합니다. 2) std :: is_trivicial_copyable을 사용하여 벡터 복사를 최적화하고 유형에 따라 다른 사본 전략을 선택하십시오. 3) 컴파일 타임 의사 결정, 유형 안전, 성능 최적화 및 코드 복잡성에주의하십시오. 타이피트 라이트를 합리적으로 사용하면 코드 품질을 크게 향상시킬 수 있습니다.

MySQL에서 문자 세트 및 콜라주를 구성하는 방법은 다음과 같습니다. 1. 서버 수준에서 문자 세트 및 콜라주 설정 : setNames'Utf8 '; setcharactersetutf8; setCollation_connection = 'utf8_general_ci'; 2. 특정 문자 세트 및 콜라주를 사용하는 데이터베이스를 만듭니다. createAbaseexample_DBCHARACTERSETUTF8COLLATEUTF8_GENERAL_CI; 3. 테이블을 만들 때 문자 세트 및 콜라주를 지정하십시오 : CreateTableAmplipt_table (idint
