前言
最近周末闲着没事,就想建个站玩玩.于是就想起了后台登陆验证的方式是不是可以玩个新花样.比如搞个手机扫码登录验证的方式.说干就干,研究一下扫码登录的原理(当然,如果有什么地方有更好的实现方法或者错误的地方欢迎指正).
正文
原理
手机端扫码简单,就不做阐述.
后台使用PHP
来实现.(暂时只考虑实现,先不考虑其他安全方面的问题,功能实现之后再做考虑).
基本原理:
1.Web前端展示二维码,二维码信息中包含有sessionId
, 并用轮训的方式来不断的从服务端请求手机端验证的结果.
2.手机端扫码并获取sessionId
,然后向服务器提交sessionId
以及其他数据.
3.当服务器接收到手机端提交的sessionId
后,就通过sessionId
来打通手机端和Web前端之间的session
,并标记登录状态为验证通过.
4.Web前端获取到验证通过信息后,跳转页面.
实现
iOS 部分实现:
iOS 端的实现很简单, 仅仅是扫码获取 sessionId
, 然后通过接口发送 sessionId
以及其他一些必须数据.
二维码扫描不说了, 二维码扫描成功后使用 AFNetworking
向服务器确认登录信息.
发送参数为二维码信息的 sessionId
. 服务器只要接收到 sessionId
就会标记接收到的 sessionId
所属回话为已登录状态.
/**
扫描完成回调
@param message 二维码扫描结果
*/
- (void)qrcodeScanSuccessWithMessage:(NSString *)message {
if (!message) {
NSLog(@"message = nil");
return;
}
[[AFHTTPSessionManager manager] POST:@"http://192.168.3.7:8888/qrcodelogin/home/index/deviceLogin" parameters:@{@"sessionId" : message} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 打印服务器返回信息
NSLog(@"success = %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// 打印报错信息方便调试
NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
NSLog(@"%@", errResponse);
}];
}
Web 前端实现:
通过轮询的方式向服务器确认, 移动端是否已经通过扫码验证成功. 获取到验证成功信息则跳转或者提醒登录成功(事例中仅仅只是提醒). 否则在延迟2秒后进入下一次查询,直到获取到登录成功信息为止.
只上 js
代码, html
代码略掉.
// 计数,方便测试(可以直观的查看当前调用的次数)
var count = 0;
// 请求服务器登录确认(轮询方法)
function requestCheck(){
$.post("login",{}, function(data){
// 服务器返回非 "no" 字符串就算验证通过, 否则进入下一轮查询
if (data != "no") {
$("div.loginBox").html("登录成功");
}else {
// 显示当前轮询次数
$("div.count").html(count++);
// 每次请求完成后, 延迟 2 秒, 再次进行查询
setTimeout("requestCheck()", 2000);
}
});
}
// button 点击事件响应方法
function cl() {
// 显示二维码
$("div.loginBox").html("");
// 调用轮询方法
requestCheck();
}
PHP 实现:
php 实现使用了 ThinkPHP
框架
首先是 Web 前端显示二维码页面的请求方法:
// 登录页
public function index(){
// 主要用于调试过程中, web 和移动端 session 是否已经打通
$_SESSION["username"] = "test";
echo "username = ".$_SESSION["username"];
$this->display(T('login'));
}
其次是手机移动端扫描二维码后请求登录的方法:
// 移动端确认登录
public function deviceLogin() {
$sid = I("post.sessionId");
$result;
if ($sid) {
// 先销毁当前 session
session('[destroy]');
// 再获取 sessionId 对应的 session
Session_id($sid);
Session_start();
// 设置登录状态(只要 deviceUUID 字段的值存在则视为已经登录)
$_SESSION['deviceUUID'] = "ABCDESDASDEFSDSDA";
$result["code"] = 1;
$result["username"] = $_SESSION["username"];
}else {
$result["code"] = 0;
}
echo json_encode($result);
}
最后是 Web 前端 Ajax 轮询请求确认登录状态的方法:
// web 前端 ajax 请求确认登录状态方法
public function login(){
if (isset($_SESSION['deviceUUID'])) {
// 如果 $_SESSION['deviceUUID'] 值存在, 则表明移动端确认了登录信息, 表明验证通过
// 返回登录确认信息
echo $_SESSION['deviceUUID'];
}else {
// 否则表明移动端还未确认登录,
// 返回还未确认登录信息
echo "no";
}
}
最后的最后附上生成二维码的方法:
public function qrcode(){
$message = session_id();
if ($message) {
// 引入第三方库文件
// 真实路径为(Vendor/Phpqrcode/phpqrcode.php)
Vendor('Phpqrcode.phpqrcode');
\QRcode::png($message, false, QR_ECLEVEL_L, 4, 2, false, 0xFFFFFF, 0x000000);
}else {
echo "error";
}
}
总结
1.引入phpqrcode
时候直接把phpqrcode.php
文件放在ThinkPHP/Library/Vendor/Phpqrcode
路径下.
2.调用phpqrcode
时候需要\QRcode::png
, 指定命名空间为空(因为phpqrcode
并没有使用命名空间, 如果直接调用, 就会报错).
3.关于 短轮询 和 长轮询,在一开始看了网上的介绍之后,感觉使用 长轮询 优点那么多, 就决定使用 长轮询, 结果尝试了之后发现如果在开启了session
的站点使用 长轮询 会造成其他请求的阻塞(之后会再写一篇文章讨论这个问题,可以先参考这个帖子).因为当时并没有看到这个帖子,所以最终暂时选择了 短轮询 来实现.