前言
最近周末閑著沒事,就想建個(gè)站玩玩.于是就想起了后臺(tái)登陸驗(yàn)證的方式是不是可以玩?zhèn)€新花樣.比如搞個(gè)手機(jī)掃碼登錄驗(yàn)證的方式.說(shuō)干就干,研究一下掃碼登錄的原理(當(dāng)然,如果有什么地方有更好的實(shí)現(xiàn)方法或者錯(cuò)誤的地方歡迎指正).
正文
原理
手機(jī)端掃碼簡(jiǎn)單,就不做闡述.
后臺(tái)使用PHP來(lái)實(shí)現(xiàn).(暫時(shí)只考慮實(shí)現(xiàn),先不考慮其他安全方面的問(wèn)題,功能實(shí)現(xiàn)之后再做考慮).
基本原理:
1.Web前端展示二維碼,二維碼信息中包含有sessionId, 并用輪訓(xùn)的方式來(lái)不斷的從服務(wù)端請(qǐng)求手機(jī)端驗(yàn)證的結(jié)果.
2.手機(jī)端掃碼并獲取sessionId,然后向服務(wù)器提交sessionId以及其他數(shù)據(jù).
3.當(dāng)服務(wù)器接收到手機(jī)端提交的sessionId后,就通過(guò)sessionId來(lái)打通手機(jī)端和Web前端之間的session,并標(biāo)記登錄狀態(tài)為驗(yàn)證通過(guò).
4.Web前端獲取到驗(yàn)證通過(guò)信息后,跳轉(zhuǎn)頁(yè)面.
實(shí)現(xiàn)
iOS 部分實(shí)現(xiàn):
iOS 端的實(shí)現(xiàn)很簡(jiǎn)單, 僅僅是掃碼獲取 sessionId, 然后通過(guò)接口發(fā)送 sessionId 以及其他一些必須數(shù)據(jù).
二維碼掃描不說(shuō)了, 二維碼掃描成功后使用 AFNetworking 向服務(wù)器確認(rèn)登錄信息.
發(fā)送參數(shù)為二維碼信息的 sessionId. 服務(wù)器只要接收到 sessionId 就會(huì)標(biāo)記接收到的 sessionId 所屬回話為已登錄狀態(tài).
/**
掃描完成回調(diào)
@param message 二維碼掃描結(jié)果
*/
- (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) {
// 打印服務(wù)器返回信息
NSLog(@"success = %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// 打印報(bào)錯(cuò)信息方便調(diào)試
NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
NSLog(@"%@", errResponse);
}];
}
Web 前端實(shí)現(xiàn):
通過(guò)輪詢的方式向服務(wù)器確認(rèn), 移動(dòng)端是否已經(jīng)通過(guò)掃碼驗(yàn)證成功. 獲取到驗(yàn)證成功信息則跳轉(zhuǎn)或者提醒登錄成功(事例中僅僅只是提醒). 否則在延遲2秒后進(jìn)入下一次查詢,直到獲取到登錄成功信息為止.
只上 js 代碼, html 代碼略掉.
// 計(jì)數(shù),方便測(cè)試(可以直觀的查看當(dāng)前調(diào)用的次數(shù))
var count = 0;
// 請(qǐng)求服務(wù)器登錄確認(rèn)(輪詢方法)
function requestCheck(){
$.post("login",{}, function(data){
// 服務(wù)器返回非 "no" 字符串就算驗(yàn)證通過(guò), 否則進(jìn)入下一輪查詢
if (data != "no") {
$("div.loginBox").html("登錄成功");
}else {
// 顯示當(dāng)前輪詢次數(shù)
$("div.count").html(count++);
// 每次請(qǐng)求完成后, 延遲 2 秒, 再次進(jìn)行查詢
setTimeout("requestCheck()", 2000);
}
});
}
// button 點(diǎn)擊事件響應(yīng)方法
function cl() {
// 顯示二維碼
$("div.loginBox").html("");
// 調(diào)用輪詢方法
requestCheck();
}
PHP 實(shí)現(xiàn):
php 實(shí)現(xiàn)使用了 ThinkPHP 框架
首先是 Web 前端顯示二維碼頁(yè)面的請(qǐng)求方法:
// 登錄頁(yè)
public function index(){
// 主要用于調(diào)試過(guò)程中, web 和移動(dòng)端 session 是否已經(jīng)打通
$_SESSION["username"] = "test";
echo "username = ".$_SESSION["username"];
$this->display(T('login'));
}
其次是手機(jī)移動(dòng)端掃描二維碼后請(qǐng)求登錄的方法:
// 移動(dòng)端確認(rèn)登錄
public function deviceLogin() {
$sid = I("post.sessionId");
$result;
if ($sid) {
// 先銷毀當(dāng)前 session
session('[destroy]');
// 再獲取 sessionId 對(duì)應(yīng)的 session
Session_id($sid);
Session_start();
// 設(shè)置登錄狀態(tài)(只要 deviceUUID 字段的值存在則視為已經(jīng)登錄)
$_SESSION['deviceUUID'] = "ABCDESDASDEFSDSDA";
$result["code"] = 1;
$result["username"] = $_SESSION["username"];
}else {
$result["code"] = 0;
}
echo json_encode($result);
}
最后是 Web 前端 Ajax 輪詢請(qǐng)求確認(rèn)登錄狀態(tài)的方法:
// web 前端 ajax 請(qǐng)求確認(rèn)登錄狀態(tài)方法
public function login(){
if (isset($_SESSION['deviceUUID'])) {
// 如果 $_SESSION['deviceUUID'] 值存在, 則表明移動(dòng)端確認(rèn)了登錄信息, 表明驗(yàn)證通過(guò)
// 返回登錄確認(rèn)信息
echo $_SESSION['deviceUUID'];
}else {
// 否則表明移動(dòng)端還未確認(rèn)登錄,
// 返回還未確認(rèn)登錄信息
echo "no";
}
}
最后的最后附上生成二維碼的方法:
public function qrcode(){
$message = session_id();
if ($message) {
// 引入第三方庫(kù)文件
// 真實(shí)路徑為(Vendor/Phpqrcode/phpqrcode.php)
Vendor('Phpqrcode.phpqrcode');
\QRcode::png($message, false, QR_ECLEVEL_L, 4, 2, false, 0xFFFFFF, 0x000000);
}else {
echo "error";
}
}
總結(jié)
1.引入phpqrcode時(shí)候直接把phpqrcode.php文件放在ThinkPHP/Library/Vendor/Phpqrcode路徑下.
2.調(diào)用phpqrcode時(shí)候需要\QRcode::png, 指定命名空間為空(因?yàn)?code>phpqrcode并沒有使用命名空間, 如果直接調(diào)用, 就會(huì)報(bào)錯(cuò)).
3.關(guān)于 短輪詢 和 長(zhǎng)輪詢,在一開始看了網(wǎng)上的介紹之后,感覺使用 長(zhǎng)輪詢 優(yōu)點(diǎn)那么多, 就決定使用 長(zhǎng)輪詢, 結(jié)果嘗試了之后發(fā)現(xiàn)如果在開啟了session的站點(diǎn)使用 長(zhǎng)輪詢 會(huì)造成其他請(qǐng)求的阻塞(之后會(huì)再寫一篇文章討論這個(gè)問(wèn)題,可以先參考這個(gè)帖子).因?yàn)楫?dāng)時(shí)并沒有看到這個(gè)帖子,所以最終暫時(shí)選擇了 短輪詢 來(lái)實(shí)現(xiàn).