詳解基于Android App 安全登錄認(rèn)證解決方案
近幾年移動(dòng)互聯(lián)網(wǎng)的高速發(fā)展,智能手機(jī)的使用用戶呈現(xiàn)爆炸性增長(zhǎng),手機(jī)終端上的App 種類繁多,大多數(shù)App 都需要與后臺(tái)系統(tǒng)進(jìn)行交互,交互的第一步需要進(jìn)行登錄認(rèn)證,過于簡(jiǎn)單的認(rèn)證方式可能被破解從而造成用戶信息的泄露甚至威脅著用戶的財(cái)產(chǎn)安全。為此基于Android 系統(tǒng),對(duì)比現(xiàn)有幾種常見的App 登錄認(rèn)證方式,并提出一種采用RSA 非對(duì)稱加密和加入Token 時(shí)效機(jī)制的登錄認(rèn)證解決方案。在登錄驗(yàn)證階段采用RSA 非對(duì)稱加密方式,App 端對(duì)服務(wù)器端返回的Token 信息加上時(shí)間戳,將處理后的Token 信息保存到本地,后面的每次請(qǐng)求都攜帶該Token 從而實(shí)現(xiàn)免登錄的登錄狀態(tài)的保持。
1. 登錄認(rèn)證方式
1.1 Web登錄認(rèn)證方式
目前常見的Web認(rèn)證的方式主要有三種:
(1)HTTP Basic Auth。 這種方式就是每次請(qǐng)求服務(wù)器時(shí)都攜帶用戶名和密碼,優(yōu)點(diǎn)是使用非常簡(jiǎn)單,缺點(diǎn)也非常明顯,因?yàn)槊看味夹枰獢y帶用戶名和密碼,很有可能造成密碼被截獲。
(2)OAuth。一種開放的授權(quán)標(biāo)準(zhǔn),允許第三方應(yīng)用訪問用戶在某一個(gè)服務(wù)商服務(wù)器上存儲(chǔ)的私密數(shù)據(jù),其處理流程先是第三方應(yīng)用通過App Key和App secret換取OAuth_Token進(jìn)行授權(quán)(此時(shí)顏色有可能需要輸入用戶名和密碼),授權(quán)完成后服務(wù)商頁(yè)面會(huì)跳轉(zhuǎn)到第三方應(yīng)用同時(shí)返回Access Token,此后第三方應(yīng)用就可以通過這個(gè)Access Token去服務(wù)商服務(wù)器中訪問相應(yīng)數(shù)據(jù)。
(3)Cookie Auth。Cookie認(rèn)證機(jī)制就是瀏覽器在發(fā)起一次登錄認(rèn)證請(qǐng)求時(shí),服務(wù)端驗(yàn)證通過后將會(huì)在產(chǎn)生一段Cookie信息并返回給瀏覽器,瀏覽器會(huì)將其保存到本地,以后的每次請(qǐng)求都會(huì)使用該 Cookie信息而不再進(jìn)行登錄驗(yàn)證。
1.2 App登錄認(rèn)證方式
由于App客戶端無法處理Cookie信息,因此App登錄認(rèn)證無法使用Web認(rèn)證方式中的Cookie認(rèn)證方式,為了登錄狀態(tài)的保持,一般會(huì)模擬Cookie認(rèn)證方式,即在App端發(fā)起登錄認(rèn)證請(qǐng)求后,得到服務(wù)端驗(yàn)證成功的確認(rèn)之后,App端一般會(huì)保存一些狀態(tài)信息在本地,后面每次請(qǐng)求都是攜帶該狀態(tài)信息,根據(jù)狀態(tài)信息的不同,可以分為如下兩種:
(1) 保存用戶信息表中的某個(gè)唯一標(biāo)識(shí)。App端發(fā)起登錄請(qǐng)求,服務(wù)器端在驗(yàn)證成功之后一般會(huì)將該登錄用戶的信息返回給客戶端,客戶端此時(shí)可以將用戶信息中的某個(gè)唯一標(biāo)識(shí)字段給保存下來,如使用SharedPreference進(jìn)行保存,后面每次發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),先判斷本地是否存在該字段,如果不存在說明用戶沒有進(jìn)行登錄認(rèn)證,跳轉(zhuǎn)到登錄頁(yè);如果存在,則直接將這個(gè)字段攜帶進(jìn)請(qǐng)求信息中,從而實(shí)現(xiàn)登錄保持狀態(tài)。這種方式優(yōu)點(diǎn)是比較簡(jiǎn)單,缺點(diǎn)就是如果保存的字段容易被別人截獲,缺乏安全性。
(2)保存Token信息。App中非常常用的一種登錄認(rèn)證方式,他的實(shí)現(xiàn)過程是,由App端發(fā)起登錄請(qǐng)求,服務(wù)器端在驗(yàn)證成功后生成一份Token信息保存到用戶表中并設(shè)置一定的時(shí)效,同時(shí)將此Token返回給App端,App端將此Token保存到本地,以后的每次發(fā)起請(qǐng)求都是用該Token。與前面一種方式相比,避免了用戶表中信息的泄露,相對(duì)更加安全。其流程圖如下:
這種方式相對(duì)于第一種來說更加安全,但還是存在著明顯的安全漏洞,需要進(jìn)行優(yōu)化。本文將以這種方案為基礎(chǔ),提出一種更加安全的基于Android平臺(tái)的App登錄解決方案。這里我們把現(xiàn)有的這種方案成為Token認(rèn)證機(jī)制,本文提出的方案成為改進(jìn)的Token認(rèn)證機(jī)制。
2. 改進(jìn)的Token認(rèn)證機(jī)制詳細(xì)設(shè)計(jì)
上述Token認(rèn)證機(jī)制也是存在著一些明顯的安全漏洞,本文提出的改進(jìn)的Token認(rèn)證機(jī)制就是基于對(duì)原來Token認(rèn)證機(jī)制中安全漏洞的優(yōu)化。對(duì)于登錄認(rèn)證機(jī)制,我們可以把它分為登錄驗(yàn)證,狀態(tài)保持和登出三個(gè)階段,改進(jìn)的Token認(rèn)證機(jī)制主要是在登錄驗(yàn)證和狀態(tài)保持階段進(jìn)行優(yōu)化。
2.1 登錄驗(yàn)證優(yōu)化
登錄驗(yàn)證階段是指App客戶端向服務(wù)器端發(fā)起登錄認(rèn)證請(qǐng)求,并攜帶用戶名和密碼,服務(wù)器端收到請(qǐng)求后獲取用戶名和密碼,并向數(shù)據(jù)庫(kù)進(jìn)行查詢驗(yàn)證的階段。由于這一階段需要密碼的傳輸,很多情況下可能都是明文或者簡(jiǎn)單的MD5加密后直接傳輸,一旦被黑客截獲可能造成密碼的泄露風(fēng)險(xiǎn)。因此這一階段的優(yōu)化就是加強(qiáng)密碼加密功能,這里我們采用RSA非對(duì)稱加密方式。
RSA是一種非對(duì)稱加密,它是對(duì)稱加密的一種加強(qiáng)版,使用對(duì)稱加密的服務(wù)器端和客戶端都使用同一種加密規(guī)則,因此在服務(wù)器端生成加密密鑰之后需要傳遞給客戶端,客戶端也需要保存這個(gè)密鑰,而傳遞密鑰的過程和保存密鑰在客戶端后都有可能發(fā)生密鑰的截獲造成安全漏洞。而非對(duì)稱加密方式會(huì)在服務(wù)器端生成兩套密鑰,生成的公鑰是公開的并傳給客戶端,私鑰保存在服務(wù)器端,客戶端用公鑰加密信息后傳遞到服務(wù)器端,服務(wù)器端再用私鑰進(jìn)行解密,因此只要私鑰不泄露,通信就是安全的。
由上面的分析可以知道,要使用RSA加密方式先要讓服務(wù)器生成公鑰和私鑰,并將公鑰返回給客戶端,因此,在圖1 的登錄驗(yàn)證階段需要額外進(jìn)行一次請(qǐng)求獲取公鑰(這個(gè)過程只需要一次,只要獲得了公鑰以后登錄認(rèn)證就不再需要該過程),其流程如圖2所示。
根據(jù)上面的分析, 可以看到這一階段最核心的是服務(wù)器端公鑰和密鑰的生成過程以及利用公鑰加密和利用私鑰解密的過程。其核心代碼如下:
/** * 初始化密鑰 * * @return * @throws Exception */ public static Map<String, Object> initKey() throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator .getInstance(KEY_ALGORITHM); keyPairGen.initialize(1024); KeyPair keyPair = keyPairGen.generateKeyPair(); // 公鑰 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 私鑰 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, Object> keyMap = new HashMap<String, Object>(2); keyMap.put(PUBLIC_KEY, publicKey); keyMap.put(PRIVATE_KEY, privateKey); return keyMap; }
/** * 取得公鑰 * * @param keyMap * @return * @throws Exception */ public static String getPublicKey(Map<String, Object> keyMap) throws Exception { Key key = (Key) keyMap.get(PUBLIC_KEY); return encryptBASE64(key.getEncoded()); }
/** * 取得私鑰 * * @param keyMap * @return * @throws Exception */ public static String getPrivateKey(Map<String, Object> keyMap) throws Exception { Key key = (Key) keyMap.get(PRIVATE_KEY); return encryptBASE64(key.getEncoded()); }
/** * 加密<br> * 用公鑰加密 * * @param data * @param key * @return * @throws Exception */ public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception { // 對(duì)公鑰解密 byte[] keyBytes = decryptBASE64(key); // 取得公鑰 X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicKey = keyFactory.generatePublic(x509KeySpec); // 對(duì)數(shù)據(jù)加密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(data); }
/** * 解密<br> * 用私鑰解密 * * @param data * @param key * @return * @throws Exception */ public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception { // 對(duì)密鑰解密 byte[] keyBytes = decryptBASE64(key); // 取得私鑰 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); // 對(duì)數(shù)據(jù)解密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(data); }
2.2 狀態(tài)保持優(yōu)化
App 客戶端在上一步登錄驗(yàn)證階段會(huì)得到服務(wù)器返回的Token 信息,并將該Token 保存到本地,以后的每次請(qǐng)求都直接攜帶該Token 而不是再次使用用戶名和密碼進(jìn)行驗(yàn)證,這一階段稱為狀態(tài)保持或登錄保持階段。前面介紹的Token 認(rèn)證機(jī)制中的Token 會(huì)一直保存在App 客戶端本地直至用戶主動(dòng)點(diǎn)擊退出按鈕,如果該Token 被截獲,截獲者同樣可以使用該Token直接訪問服務(wù)器中的敏感數(shù)據(jù)。針對(duì)Token 這一長(zhǎng)時(shí)間保存的特點(diǎn),我們的優(yōu)化就是為這個(gè)Token 設(shè)置一個(gè)生效時(shí)效,具體來說就是在從服務(wù)器獲得該Token后,在保存的時(shí)候在Token 后加上一個(gè)當(dāng)前的時(shí)間戳,后面每次發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),先取出該Token 后面的時(shí)間戳判斷有沒有超過生效時(shí)間,如果沒有則將處理后的Token 放入到請(qǐng)求信息中,如果超時(shí)了,則重新進(jìn)行登錄認(rèn)證過程。這種優(yōu)化過程是以犧牲了一點(diǎn)用戶體驗(yàn)為代價(jià)。其流程圖如下:
本文章詳細(xì)探討了常見的Web 登錄認(rèn)證方式和App登錄認(rèn)證方式, 對(duì)現(xiàn)在比較常用的App 登錄認(rèn)證方式Token 認(rèn)證機(jī)制的安全漏洞進(jìn)行了討論,在這個(gè)基礎(chǔ)上提出了改進(jìn)的Token 認(rèn)證登錄機(jī)制, 通過采用RSA 非對(duì)稱加密優(yōu)化登錄驗(yàn)證階段, 使用Token 時(shí)效機(jī)制優(yōu)化狀態(tài)保持階段, 該優(yōu)化方案在實(shí)際生產(chǎn)中得到了檢驗(yàn)。同時(shí),該方案還有進(jìn)一步優(yōu)化的空間,例如在優(yōu)化狀態(tài)保持階段時(shí)采用的是Token 時(shí)效機(jī)制, 但這樣犧牲了App 的使用體驗(yàn), 因此在以后的研究中可以針對(duì)這一階段做進(jìn)一步的優(yōu)化過程。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
代碼從windows下visual studio到andriod平臺(tái)遷移實(shí)現(xiàn)步驟
這篇文章主要介紹了代碼從windows下visual studio到andriod平臺(tái)遷移的修改記錄的相關(guān)資料,需要的朋友可以參考下2017-01-01Flutter實(shí)現(xiàn)可循環(huán)輪播圖效果
這篇文章主要介紹了Flutter實(shí)現(xiàn)可循環(huán)輪播圖效果,本文圖文并茂通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-07-07Android TabHost選項(xiàng)卡標(biāo)簽圖標(biāo)始終不出現(xiàn)的解決方法
這篇文章主要介紹了Android TabHost選項(xiàng)卡標(biāo)簽圖標(biāo)始終不出現(xiàn)的解決方法,涉及Android界面布局相關(guān)屬性與狀態(tài)設(shè)置操作技巧,需要的朋友可以參考下2019-03-03Android6.0動(dòng)態(tài)申請(qǐng)權(quán)限所遇到的問題小結(jié)
這篇文章給大家介紹了Android6.0動(dòng)態(tài)申請(qǐng)權(quán)限所遇到的問題,在沒給大家介紹這下問題之前,先給大家說下基本定義和基本使用方式,本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,對(duì)android 6.0 動(dòng)態(tài)權(quán)限遇到問題感興趣的朋友一起看看吧2016-11-11Android編程之Activity中onDestroy()調(diào)用分析
這篇文章主要介紹了Android編程之Activity中onDestroy()調(diào)用方法,針對(duì)onDestroy引起的內(nèi)存泄露及解決方法進(jìn)行了分析,并給出了解決方案,需要的朋友可以參考下2015-12-12android實(shí)現(xiàn)圓形漸變進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)圓形漸變進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Android使用setCustomTitle()方法自定義對(duì)話框標(biāo)題
Android有自帶的對(duì)話框標(biāo)題,但是不太美觀,如果要給彈出的對(duì)話框設(shè)置一個(gè)自定義的標(biāo)題,使用AlertDialog.Builder的setCustomTitle()方法非常方便,接下來通過本文給大家介紹Android使用setCustomTitle()方法自定義對(duì)話框標(biāo)題,感興趣的朋友一起學(xué)習(xí)吧2016-02-02MobLink Android端業(yè)務(wù)場(chǎng)景簡(jiǎn)單說明
這篇文章主要介紹了MobLink Android端業(yè)務(wù)場(chǎng)景簡(jiǎn)單說明,MobLink的功能實(shí)現(xiàn)就是在分享前會(huì)將鏈接的參數(shù)信息保存到服務(wù)器,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-09-09