近發現群內大伙對用做的APP怎么做登錄功能以及維護登錄狀態非常困惑,而我前一段時間正好稍微研究了一下,所以把我知道的告訴大家,節約大家查找資料的時間。
你是否真的需要登錄功能?
把這個問題放在最前面并不是灌水,而是真的見過很多并不需要登錄的APP去做了登錄功能,或者是并不需要強制登錄的APP把登錄作為啟動頁。
用戶對你的APP一無所知,你就要求對方注冊并登錄,除非APP本身已經很有名氣或者是用戶有強需求,否則正常人應該會直接把它刪掉。
比較溫和的方式是將一些并不需要登錄,但可以給用戶帶來幫助的東西,第一時間展現給他們,讓他們產生興趣,再在合適的時機引導他們注冊(比如使用需要使用更高級的功能,或用戶需要收藏某個喜歡的信息時)。
登錄和注冊要足夠簡單
這是小小的手機端,用再好的輸入法,打字也是不方便的,所以別把登錄頁設計得需要填很多東西。如果有可能的話,只填手機號,讓用戶收到短信驗證碼就完成注冊是最好不過的了。想獲得更多信息?想想大公司的APP是怎么做的,他們會告訴用戶,現在的個人資料完善程度是30%,如果想獲得更多積分,你需要填完。
tips:如果你想發布在并且同時包含注冊功能,那么注冊頁面必須做一個用戶許可協議的鏈接,否則有可能通不過審核。
實現登錄后的有幾種方式? APP當瀏覽器用,直接載入遠程頁面
這種情況是很多偷懶的程序員或者傻X的老板選擇的方式,因為做起來實在太快。如果本身網站是響應式布局,那么很有可能不需要做什么更改,就只要在開發時打開首頁就好了,這樣的APP外殼就純粹成為了一個瀏覽器。
但比起這樣做帶來的無數缺點來,開發速度快的優點幾乎可以忽略不計。
首先,在網絡環境不佳時,純大白頁,用戶體驗0;
然后,CSS和JS等資源不在本地,需要遠程載入,如果使用了之類的框架,那用戶為了開一下APP而耗費的流量真是令人感動;
再然后,網頁里常用的,在手機的里速度并不理想,而如果是非ajax的網頁那就更糟心了,每次操作都要跳轉和頁面渲染,要讓人把它當成APP那實在是笑話。
再再然后,這樣的所謂APP,要通過的審查,那是做夢的(除非審核員當天鬧肚子嚴重,拿著紙巾奔向廁所前誤點了通過……),蘋果的要求是,這得是APP,而不能是某個網站做成APP的樣子,那樣的情況適合做Web APP。而據我所知,國內幾個較大的市場,這樣的APP也是無法通過審核的。
調用后端接口
這是個很好的時代,因為無論后端你是用Java、PHP,還是node.js,都可以通過xml、json來和APP通訊。遙想當年寫服務端要自己寫包結構,然后為了解決并發問題還折騰了半年IOCP模型,真心覺得現在太幸福了。
把剛才那個用APP當瀏覽器使的案例的所有缺點反過來看,就是這樣做的優點,在優化完善的情況下體驗接近原生,而且通訊流量極少,通過各種審核也是妥妥的。
tips:通過plus對象中的來Get、Post遠程的后端接口,或者使用Mui中封裝好的。
插一段代碼,我把mui的ajax又做了進一步的封裝,對超時進行了自動重試,而對等情況也做相應處理:
;mui.web_query = function(func_url, params, onSuccess, onError, retry){
var onSuccess = arguments[2]?arguments[2]:function(){};
var onError = arguments[3]?arguments[3]:function(){};
var retry = arguments[4]?arguments[4]:3;
func_url = 'http://www.xxxxxx.com/ajax/?fn=' + func_url;
mui.ajax(func_url, {
data:params,
dataType:'json',
type:'post',
timeout:3000,
success:function(data){

if(data.err === 'ok'){
onSuccess(data);
}
else{
onError(data.code);
}
},
error:function(xhr,type,errorThrown){
retry--;
if(retry > 0) return mui.web_query(func_url, params, onSuccess, onError, retry);
onError('FAILED_NETWORK');
}
})
};
var onError = function(errcode){
switch(errcode){
case 'FAILED_NETWORK':
mui.toast('網絡不佳');
break;
case 'INVALID_TOKEN':
wv_login.show();
break;
default:
console.log(errcode);
}
};
var params = {per:10, pageno:coms_current_pageno};
mui.web_query('get_com_list', params, onSuccess, onError, 3);
調用后端接口怎么樣才安全? 在APP中保存登錄數據,每次調用接口時傳輸
程序員總能給自己找到偷懶的方法,有的程序為了省事,會在用戶登錄后,直接把用戶名和密碼保存在本地,然后每次調用后端接口時作為參數傳遞。真省事兒啊!可這種方法簡單就像拿著一袋子錢在路上邊走邊喊“快來搶我呀!快來搶我呀!”,一個小小的嗅探器就能把用戶的密碼拿到手,如果用戶習慣在所有地方用一個密碼,那么你闖大禍了,黑客通過撞庫的方法能把用戶的所有信息一鍋端。
登錄時請求一次token,之后用token調用接口
這是比較安全的方式,用戶在登錄時,APP調用獲取token的接口(比如),用post將用戶名和密碼的摘要傳遞給服務器,然后服務器比對數據庫中的用戶信息,匹配則返回綁定該用戶的token(這一般翻譯為令牌,很直觀的名字,一看就知道是有了這玩意,就會對你放行),而數據庫中,在用戶的token表中也同時插入了這個token相關的數據:這個token屬于誰?這個token的有效期是多久?這個token當前登錄的ip地址是?這個token對應的是?……
這樣即便token被有心人截獲,也不會造成太大的安全風險。因為沒有用戶名和密碼,然后如果黑客通過這個token偽造用戶請求,我們在服務器端接口被調用時就可以對發起請求的ip地址、user-agent之類的信息作比對,以防止偽造。再然后,如果token的有效期設得小,過一會兒它就過期了,除非黑客可以持續截獲你的token,否則他只能干瞪眼。(插一句題外話:看到這里,是不是明白為什么不推薦在外面隨便接入來歷不明的wifi熱點了?)
tips:token如何生成? 可以根據用戶的信息及一些隨機信息(比如時間戳)再通過hash編碼(比如md5、sha1等)生成唯一的編碼。
tips:token的安全級別,取決于你的實際需求,所以如果不是涉及財產安全的領域,并不建議太嚴格(比如用戶走著走著,3G換了個基站,閃斷了一下IP地址變了,尼瑪token過期了,這就屬于為了不必要的安全丟了用戶體驗,當然如果變換的IP地址跨省的話還是應該驗證一下的,想想QQ有時候會讓填驗證碼就明白了)。
tips:接口在返回信息時,可以包含本次請求的狀態,比如成功調用,那么['']可能就是'',而反之則是'error',而如果是'error',則['']中就可以包含錯誤的原因,比如中是''就可以告訴APP這個token過期或無效,這時APP應彈出登錄框或者用本地存儲的用戶名或密碼再次請求token(用戶選擇“記住密碼”,就應該在本地保存用戶名和密碼的摘要,方法見plus.的文檔)。
再插點代碼,基于plus.的用戶信息類,注意:需要在之后再使用。
;function UserInfo(){
};
//清除登錄信息
UserInfo.clear = function(){
plus.storage.removeItem('username');
plus.storage.removeItem('password');
plus.storage.removeItem('token');
}
//檢查是否包含自動登錄的信息
UserInfo.auto_login = function(){
var username = UserInfo.username();
var pwd = UserInfo.password();
if(!username || !pwd){
return false;
}
return true;
}
//檢查是否已登錄
UserInfo.has_login = function(){
var username = UserInfo.username();

var pwd = UserInfo.password();
var token = UserInfo.token();
if(!username || !pwd || !token){
return false;
}
return true;
};
UserInfo.username = function(){
if(arguments.length == 0){
return plus.storage.getItem('username');
}
if(arguments[0] === ''){
plus.storage.removeItem('username');
return;
}
plus.storage.setItem('username', arguments[0]);
};
UserInfo.password = function(){
if(arguments.length == 0){
return plus.storage.getItem('password');
}
if(arguments[0] === ''){
plus.storage.removeItem('password');
return;
}
plus.storage.setItem('password', arguments[0]);
};
UserInfo.token = function(){

if(arguments.length == 0){
return plus.storage.getItem('token');
}
if(arguments[0] === ''){
plus.storage.removeItem('token');
return;
}
plus.storage.setItem('token', arguments[0]);
};
這樣當用戶啟動APP或使用了需要登錄才能使用的功能時,就可以使用.()來判斷是否已經登錄,如果已登錄,則使用.token()來獲取到token數據app接口是什么意思,作為參數調用遠程的后端接口。
if(UserInfo.has_login()){
//打開需要展示給用戶的頁面,或者是調用遠端接口
}
else{
wv_login.show('slide-in-up'); //從底部向上滑出登錄頁面
}
在登錄頁面中,用戶輸入了用戶名和密碼后,并點擊了”登錄“按鈕,我們下一步做什么?再插段代碼(注意:此處使用的是我剛才代碼中擴展的函數,你也可以直接使用mui的ajax):
function get_pwd_hash(pwd){
var salt = 'hbuilder'; //此處的salt是為了避免黑客撞庫,而在md5之前對原文做一定的變形,可以設為自己喜歡的,只要和服務器驗證時的salt一致即可。
return md5(salt + pwd); //此處假設你已經引用了md5相關的庫,比如github上的JavaScript-MD5
}
//這里假設你已經通過DOM操作獲取到了用戶名和密碼,分別保存在username和password變量中。
var username = xxx;
var password = xxx;
var pwd_hash = get_pwd_hash(password);
var onSuccess = function(data){
UserInfo.username(username);

UserInfo.password(pwd_hash);
UserInfo.token(data.token); //把獲取到的token保存到storage中
var wc = plus.webview.currentWebview();
wc.hide('slide-out-bottom'); //此處假設是隱藏登錄頁回到之前的頁面,實際你也可以干點兒別的
}
var onError = function(errcode){
switch(errcode){
case 'INCORRECT_PASSWORD':
mui.toast('密碼不正確');
break;
case 'USER_NOT_EXISTS':
mui.toast('用戶尚未注冊');
break;
}
}
mui.web_query('get_token', {username:username,password:pwd_hash}, onSuccess, onError, 3);
更安全一點,獲取token通過SSL
剛才的方法,機智一點兒的讀者大概會心存疑慮:那獲取token時不還是得明文傳輸一次密碼嗎?
是的,你可以將這個獲取token的地址,用SSL來保護(比如),這樣黑客即使截了包,一時半會兒也解不出什么信息。
SSL證書的獲取渠道很多app接口是什么意思,我相信你總有辦法查到,所以不廢話了。不過話說上的SSL證書比的要便宜得多……(這是吐槽)
tips:前段時間漏洞讓很多服務器遭殃,所以如果自己搭服務器,一定記得裝補丁。
tips:可以把所有接口都弄成SSL的嗎?可以。但會拖慢服務器,如果是配置并不自信的VPS,建議不折騰。
還要更更安全(這標題真省事)
還記得剛才APP向服務器請求token時,可以加入的用戶信息嗎?比如用戶的設備。
如果我們在調用接口時,還附帶一個當前時間戳參數,同時,用和這個時間戳再生成一個參數sign,比如 md5( token)這樣的形式。而服務端首先驗證一下參數中的時間戳與當前服務器時間是否一致(誤差保持在合理范圍內即可,比如5分鐘),然后根據用戶保存在服務器中的來對參數中的時間戳進行相同的變形,驗證是否匹配,那便自然“更更安全”了。
tips:如果對整個調用請求中的參數進行排序,再以和加上排序后的參數來對整個調用生成1個sign,黑客即使截獲sign,不同的時間點、參數請求所使用的sign也是不同的,難以偽造,自然會更安全。當然,寫起來也更費事。
tips:明白了原理,整個驗證過程是可以根據自己的需求改造的。
原文出處: