前言
一般APP都是剛安裝后,第一次啟動(dòng)時(shí)需要登錄(提示你需要登錄或者直接啟動(dòng)在登錄界面)。而只要登錄成功后,以后每次啟動(dòng)時(shí)都是登錄狀態(tài),不需要每次啟動(dòng)時(shí)再次登錄。不過,也有些APP若你長期未啟動(dòng),再次啟動(dòng)時(shí),它會(huì)提示你登錄過期,讓你重新登錄。這個(gè)是怎么實(shí)現(xiàn)的?APP是怎么保持登錄狀態(tài)的?
之所以突然寫這個(gè)話題,是因?yàn)樽蛲頍o意間刷知乎刷到了這個(gè)問題iOS系統(tǒng)如何實(shí)現(xiàn)app登錄類似微信只需登錄一次,退出后不需要每次登錄?
回答里給出了好幾種解決方案,其中比較標(biāo)準(zhǔn)的方案是“帶時(shí)效檢測的token機(jī)制”。所謂token,即“令牌”的意思。那這個(gè)token機(jī)制的執(zhí)行邏輯是怎么樣的呢?
token機(jī)制
token機(jī)制的執(zhí)行邏輯可以用下面一張圖展示清楚:
當(dāng)用戶剛安裝完APP,并進(jìn)行了注冊,擁有了賬號(hào)和密碼后。此時(shí),則該進(jìn)行首次登錄了:
APP將用戶輸入的賬號(hào)和密碼提交給服務(wù)器;
服務(wù)器對(duì)其進(jìn)行校驗(yàn),若賬號(hào)和密碼對(duì)得上則校驗(yàn)通過,說明登錄成功。并生成一個(gè)token值,將其保存在數(shù)據(jù)庫,同時(shí)也返回給客戶端;
客戶端拿到返回的token值后,可將其保存在本地。作為公共參數(shù),即以后每次請(qǐng)求服務(wù)器時(shí)都攜帶該token,提交給服務(wù)器,讓服務(wù)器校驗(yàn)。
服務(wù)器接收到請(qǐng)求后,會(huì)取出請(qǐng)求頭里的token值與數(shù)據(jù)庫存儲(chǔ)的token進(jìn)行對(duì)比校驗(yàn)。若兩個(gè)token值相同,則說明用戶登錄成功過,且當(dāng)前正處于登錄狀態(tài),此時(shí)正常返回?cái)?shù)據(jù),讓APP顯示數(shù)據(jù)。若兩個(gè)值不一致,則說明原來的的登錄已經(jīng)失效,此時(shí)返回錯(cuò)誤狀態(tài)碼,提示用戶跳轉(zhuǎn)至登錄界面重新登錄。
用戶每進(jìn)行一次登錄,登錄成功后服務(wù)器都會(huì)更新個(gè)
token新值返回給客戶端。
基本的邏輯原理就是這些,下面我們看看項(xiàng)目代碼中具體是怎么寫的。
代碼
首先看在登錄界面發(fā)送登錄請(qǐng)求那塊。一開始我以為服務(wù)器返回的token值會(huì)在響應(yīng)數(shù)據(jù)體中,也就是和用戶信息在一起。但是我看了接口,并沒有發(fā)現(xiàn)與token對(duì)應(yīng)的字段。后來仔細(xì)看了下代碼,原來token是在響應(yīng)頭中的。上面邏輯里的無論是存儲(chǔ)token值,還是攜帶token作為公共參數(shù),都是在網(wǎng)絡(luò)層完成的。這一切都發(fā)生在網(wǎng)絡(luò)層,而不麻煩業(yè)務(wù)層,這很優(yōu)雅。
每當(dāng)?shù)卿洺晒?,服?wù)器在響應(yīng)頭中返回新的token值,將其保存在本地:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
_httpResponse = (NSHTTPURLResponse *)response;
if(_httpResponse && [_httpResponse respondsToSelector:@selector(allHeaderFields)])
{
NSDictionary *httpResponseHeaderFields = [_httpResponse allHeaderFields];
NSNumber *totle=[NSNumber numberWithLongLong:[[httpResponseHeaderFields objectForKey:@"Content-Length"] longLongValue]];
_dataSize=[totle integerValue];
if (_requestInterface == RequestInterfaceLogin ||_requestInterface == RequestInterfaceVerifyCodeLogin || _requestInterface == RequestInterfaceRegister) {//設(shè)置token
NSString *tokenString = httpResponseHeaderFields[@"x-auth-token"];
[REDUserModel saveToken:tokenString];
RUNDUG(@"token------%@",tokenString);
}
}
}
+ (void)saveToken:(NSString *)token
{
if (token.length == 0) {
return;
}
[[NSUserDefaults standardUserDefaults]setObject:token forKey:kTokenKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
除了在成功登錄的回調(diào)方法里存儲(chǔ)token值外,也可以在內(nèi)存和磁盤存儲(chǔ)一個(gè)登錄狀態(tài),表示是否是登錄狀態(tài)。下次啟動(dòng)APP時(shí),我只需要從本地獲取該值,看其是登錄狀態(tài)否?然后決定界面怎么顯示,或者點(diǎn)擊界面上的按鈕去執(zhí)行什么事件,是去完成業(yè)務(wù)動(dòng)作,還是彈出登錄界面讓用戶重新登錄。
(這個(gè)可以寫在網(wǎng)絡(luò)層的回調(diào)方法里,也可以寫在業(yè)務(wù)層的回調(diào)方法里,都沒問題。)
[REDUserModel shareInstance].isLogin = YES;
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kUserLoginStatus];
[[NSUserDefaults standardUserDefaults] synchronize];
在每次發(fā)送非登錄請(qǐng)求接口時(shí),將token作為公共參數(shù)放入請(qǐng)求頭中提交給服務(wù)器,服務(wù)器對(duì)其進(jìn)行校驗(yàn),判斷是登錄狀態(tài)否?還是太久未登錄需要重新登錄。然后返回相應(yīng)的狀態(tài)碼。
if (interface != RequestInterfaceLogin) {
[_request setValue:[REDUserModel shareInstance].token forHTTPHeaderField:@"x-auth-token"];
}```
---
# 結(jié)尾
總結(jié),用``token``機(jī)制完成登錄狀態(tài)保持/身份認(rèn)證,生成怎樣的``token``,怎么進(jìn)行``token``校驗(yàn)都是服務(wù)器完成的。其實(shí)客戶端的工作很簡單,就是保存服務(wù)器給的``token``,然后將其作為請(qǐng)求服務(wù)器的公共參數(shù)。