http是無(wú)狀態(tài)的,一次請(qǐng)求結(jié)束,連接斷開,下次服務(wù)器再收到請(qǐng)求,它就不知道這個(gè)請(qǐng)求是哪個(gè)用戶發(fā)過(guò)來(lái)的。當(dāng)然它知道是哪個(gè)客戶端地址發(fā)過(guò)來(lái)的,但是對(duì)于我們的應(yīng)用來(lái)說(shuō),我們是靠用戶來(lái)管理,而不是靠客戶端。所以對(duì)我們的應(yīng)用而言,它是需要有狀態(tài)管理的,以便服務(wù)端能夠準(zhǔn)確的知道http請(qǐng)求是哪個(gè)用戶發(fā)起的,從而判斷他是否有權(quán)限繼續(xù)這個(gè)請(qǐng)求。這個(gè)過(guò)程就是常說(shuō)的會(huì)話管理。它也可以簡(jiǎn)單理解為一個(gè)用戶從登錄到退出應(yīng)用的一段期間。本文總結(jié)了3種常見的實(shí)現(xiàn)web應(yīng)用會(huì)話管理的方式:
1)基于server端session的管理方式
2)cookie-base的管理方式
3)token-base的管理方式
這些內(nèi)容可以幫助加深對(duì)web中用戶登錄機(jī)制的理解,對(duì)實(shí)際項(xiàng)目開發(fā)也有參考價(jià)值,歡迎閱讀與指正。
在早期web應(yīng)用中,通常使用服務(wù)端session來(lái)管理用戶的會(huì)話。快速了解服務(wù)端session:
1) 服務(wù)端session是用戶第一次訪問(wèn)應(yīng)用時(shí),服務(wù)器就會(huì)創(chuàng)建的對(duì)象,代表用戶的一次會(huì)話過(guò)程,可以用來(lái)存放數(shù)據(jù)。服務(wù)器為每一個(gè)session都分配一個(gè)唯一的sessionid,以保證每個(gè)用戶都有一個(gè)不同的session對(duì)象。
2)服務(wù)器在創(chuàng)建完session后,會(huì)把sessionid通過(guò)cookie返回給用戶所在的瀏覽器,這樣當(dāng)用戶第二次及以后向服務(wù)器發(fā)送請(qǐng)求的時(shí)候,就會(huì)通過(guò)cookie把sessionid傳回給服務(wù)器,以便服務(wù)器能夠根據(jù)sessionid找到與該用戶對(duì)應(yīng)的session對(duì)象。
3)session通常有失效時(shí)間的設(shè)定,比如2個(gè)小時(shí)。當(dāng)失效時(shí)間到,服務(wù)器會(huì)銷毀之前的session,并創(chuàng)建新的session返回給用戶。但是只要用戶在失效時(shí)間內(nèi),有發(fā)送新的請(qǐng)求給服務(wù)器,通常服務(wù)器都會(huì)把他對(duì)應(yīng)的session的失效時(shí)間根據(jù)當(dāng)前的請(qǐng)求時(shí)間再延長(zhǎng)2個(gè)小時(shí)。
4)session在一開始并不具備會(huì)話管理的作用。它只有在用戶登錄認(rèn)證成功之后,并且往sesssion對(duì)象里面放入了用戶登錄成功的憑證,才能用來(lái)管理會(huì)話。管理會(huì)話的邏輯也很簡(jiǎn)單,只要拿到用戶的session對(duì)象,看它里面有沒(méi)有登錄成功的憑證,就能判斷這個(gè)用戶是否已經(jīng)登錄。當(dāng)用戶主動(dòng)退出的時(shí)候,會(huì)把它的session對(duì)象里的登錄憑證清掉。所以在用戶登錄前或退出后或者session對(duì)象失效時(shí),肯定都是拿不到需要的登錄憑證的。
主流的web開發(fā)平臺(tái)(java,.net,php)都原生支持這種會(huì)話管理的方式,而且開發(fā)起來(lái)很簡(jiǎn)單,相信大部分后端開發(fā)人員在入門的時(shí)候都了解并使用過(guò)它。它還有一個(gè)比較大的優(yōu)點(diǎn)就是安全性好,因?yàn)樵跒g覽器端與服務(wù)器端保持會(huì)話狀態(tài)的媒介始終只是一個(gè)sessionid串,只要這個(gè)串夠隨機(jī),攻擊者就不能輕易冒充他人的sessionid進(jìn)行操作;除非通過(guò)CSRF或http劫持的方式,才有可能冒充別人進(jìn)行操作;即使冒充成功,也必須被冒充的用戶session里面包含有效的登錄憑證才行。但是在真正決定用它管理會(huì)話之前,也得根據(jù)自己的應(yīng)用情況考慮以下幾個(gè)問(wèn)題:
1)這種方式將會(huì)話信息存儲(chǔ)在web服務(wù)器里面,所以在用戶同時(shí)在線量比較多時(shí),這些會(huì)話信息會(huì)占據(jù)比較多的內(nèi)存;
2)當(dāng)應(yīng)用采用集群部署的時(shí)候,會(huì)遇到多臺(tái)web服務(wù)器之間如何做session共享的問(wèn)題。因?yàn)閟ession是由單個(gè)服務(wù)器創(chuàng)建的,但是處理用戶請(qǐng)求的服務(wù)器不一定是那個(gè)創(chuàng)建session的服務(wù)器,這樣他就拿不到之前已經(jīng)放入到session中的登錄憑證之類的信息了;
3)多個(gè)應(yīng)用要共享session時(shí),除了以上問(wèn)題,還會(huì)遇到跨域問(wèn)題,因?yàn)椴煌膽?yīng)用可能部署的主機(jī)不一樣,需要在各個(gè)應(yīng)用做好cookie跨域的處理。
針對(duì)問(wèn)題1和問(wèn)題2,我見過(guò)的解決方案是采用redis這種中間服務(wù)器來(lái)管理session的增刪改查,一來(lái)減輕web服務(wù)器的負(fù)擔(dān),二來(lái)解決不同web服務(wù)器共享session的問(wèn)題。針對(duì)問(wèn)題3,由于服務(wù)端的session依賴cookie來(lái)傳遞sessionid,所以在實(shí)際項(xiàng)目中,只要解決各個(gè)項(xiàng)目里面如何實(shí)現(xiàn)sessionid的cookie跨域訪問(wèn)即可,這個(gè)是可以實(shí)現(xiàn)的,就是比較麻煩,前后端有可能都要做處理。
如果不考慮以上三個(gè)問(wèn)題,這種管理方式比較值得使用,尤其是一些小型的web應(yīng)用。但是一旦應(yīng)用將來(lái)有擴(kuò)展的必要,那就得謹(jǐn)慎對(duì)待前面的三個(gè)問(wèn)題。如果真要在項(xiàng)目中使用這種方式,推薦結(jié)合單點(diǎn)登錄框架如CAS一起用,這樣會(huì)使應(yīng)用的擴(kuò)展性更強(qiáng)。
由于前一種方式會(huì)增加服務(wù)器的負(fù)擔(dān)和架構(gòu)的復(fù)雜性,所以后來(lái)就有人想出直接把用戶的登錄憑證直接存到客戶端的方案,當(dāng)用戶登錄成功之后,把登錄憑證寫到cookie里面,并給cookie設(shè)置有效期,后續(xù)請(qǐng)求直接驗(yàn)證存有登錄憑證的cookie是否存在以及憑證是否有效,即可判斷用戶的登錄狀態(tài)。使用它來(lái)實(shí)現(xiàn)會(huì)話管理的整體流程如下:
1)用戶發(fā)起登錄請(qǐng)求,服務(wù)端根據(jù)傳入的用戶密碼之類的身份信息,驗(yàn)證用戶是否滿足登錄條件,如果滿足,就根據(jù)用戶信息創(chuàng)建一個(gè)登錄憑證,這個(gè)登錄憑證簡(jiǎn)單來(lái)說(shuō)就是一個(gè)對(duì)象,最簡(jiǎn)單的形式可以只包含用戶id,憑證創(chuàng)建時(shí)間和過(guò)期時(shí)間三個(gè)值。
2)服務(wù)端把上一步創(chuàng)建好的登錄憑證,先對(duì)它做數(shù)字簽名,然后再用對(duì)稱加密算法做加密處理,將簽名、加密后的字串,寫入cookie。cookie的名字必須固定(如ticket),因?yàn)楹竺嬖佾@取的時(shí)候,還得根據(jù)這個(gè)名字來(lái)獲取cookie值。這一步添加數(shù)字簽名的目的是防止登錄憑證里的信息被篡改,因?yàn)橐坏┬畔⒈淮鄹模敲聪乱徊阶龊灻?yàn)證的時(shí)候肯定會(huì)失敗。做加密的目的,是防止cookie被別人截取的時(shí)候,無(wú)法輕易讀到其中的用戶信息。
3)用戶登錄后發(fā)起后續(xù)請(qǐng)求,服務(wù)端根據(jù)上一步存登錄憑證的cookie名字,獲取到相關(guān)的cookie值。然后先做解密處理,再做數(shù)字簽名的認(rèn)證,如果這兩步都失敗,說(shuō)明這個(gè)登錄憑證非法;如果這兩步成功,接著就可以拿到原始存入的登錄憑證了。然后用這個(gè)憑證的過(guò)期時(shí)間和當(dāng)前時(shí)間做對(duì)比,判斷憑證是否過(guò)期,如果過(guò)期,就需要用戶再重新登錄;如果未過(guò)期,則允許請(qǐng)求繼續(xù)。
這種方式最大的優(yōu)點(diǎn)就是實(shí)現(xiàn)了服務(wù)端的無(wú)狀態(tài)化,徹底移除了服務(wù)端對(duì)會(huì)話的管理的邏輯,服務(wù)端只需要負(fù)責(zé)創(chuàng)建和驗(yàn)證登錄cookie即可,無(wú)需保持用戶的狀態(tài)信息。對(duì)于第一種方式的第二個(gè)問(wèn)題,用戶會(huì)話信息共享的問(wèn)題,它也能很好解決:因?yàn)槿绻皇峭粋€(gè)應(yīng)用做集群部署,由于驗(yàn)證登錄憑證的代碼都是一樣的,所以不管是哪個(gè)服務(wù)器處理用戶請(qǐng)求,總能拿到cookie中的登錄憑證來(lái)進(jìn)行驗(yàn)證;如果是不同的應(yīng)用,只要每個(gè)應(yīng)用都包含相同的登錄邏輯,那么他們也是能輕易實(shí)現(xiàn)會(huì)話共享的,不過(guò)這種情況下,登錄邏輯里面數(shù)字簽名以及加密解密要用到的密鑰文件或者密鑰串,需要在不同的應(yīng)用里面共享,總而言之,就是需要算法完全保持一致。
這種方式由于把登錄憑證直接存放客戶端,并且需要cookie傳來(lái)傳去,所以它的缺點(diǎn)也比較明顯:
1)cookie有大小限制,存儲(chǔ)不了太多數(shù)據(jù),所以要是登錄憑證存的消息過(guò)多,導(dǎo)致加密簽名后的串太長(zhǎng),就會(huì)引發(fā)別的問(wèn)題,比如其它業(yè)務(wù)場(chǎng)景需要cookie的時(shí)候,就有可能沒(méi)那么多空間可用了;所以用的時(shí)候得謹(jǐn)慎,得觀察實(shí)際的登錄cookie的大??;比如太長(zhǎng),就要考慮是非是數(shù)字簽名的算法太嚴(yán)格,導(dǎo)致簽名后的串太長(zhǎng),那就適當(dāng)調(diào)整簽名邏輯;比如如果一開始用4096位的RSA算法做數(shù)字簽名,可以考慮換成1024、2048位;
2)每次傳送cookie,增加了請(qǐng)求的數(shù)量,對(duì)訪問(wèn)性能也有影響;
3)也有跨域問(wèn)題,畢竟還是要用cookie。
相比起第一種方式,cookie-based方案明顯還是要好一些,目前好多web開發(fā)平臺(tái)或框架都默認(rèn)使用這種方式來(lái)做會(huì)話管理,比如php里面yii框架,這是我們團(tuán)隊(duì)后端目前用的,它用的就是這個(gè)方案,以上提到的那些登錄邏輯,框架也都已經(jīng)封裝好了,實(shí)際用起來(lái)也很簡(jiǎn)單;asp.net里面forms身份認(rèn)證,也是這個(gè)思路,這里有一篇好文章把它的實(shí)現(xiàn)細(xì)節(jié)都說(shuō)的很清楚:
http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html
前面兩種會(huì)話管理方式因?yàn)槎加玫絚ookie,不適合用在native app里面:native app不好管理cookie,畢竟它不是瀏覽器。這兩種方案都不適合用來(lái)做純api服務(wù)的登錄認(rèn)證。要實(shí)現(xiàn)api服務(wù)的登錄認(rèn)證,就要考慮下面要介紹的第三種會(huì)話管理方式。
這種方式從流程和實(shí)現(xiàn)上來(lái)說(shuō),跟cookie-based的方式?jīng)]有太多區(qū)別,只不過(guò)cookie-based里面寫到cookie里面的ticket在這種方式下稱為token,這個(gè)token在返回給客戶端之后,后續(xù)請(qǐng)求都必須通過(guò)url參數(shù)或者是http header的形式,主動(dòng)帶上token,這樣服務(wù)端接收到請(qǐng)求之后就能直接從http header或者url里面取到token進(jìn)行驗(yàn)證:
這種方式不通過(guò)cookie進(jìn)行token的傳遞,而是每次請(qǐng)求的時(shí)候,主動(dòng)把token加到http header里面或者url后面,所以即使在native app里面也能使用它來(lái)調(diào)用我們通過(guò)web發(fā)布的api接口。app里面還要做兩件事情:
1)有效存儲(chǔ)token,得保證每次調(diào)接口的時(shí)候都能從同一個(gè)位置拿到同一個(gè)token;
2)每次調(diào)接口的的代碼里都得把token加到header或者接口地址里面。
看起來(lái)麻煩,其實(shí)也不麻煩,這兩件事情,對(duì)于app來(lái)說(shuō),很容易做到,只要對(duì)接口調(diào)用的模塊稍加封裝即可。
這種方式同樣適用于網(wǎng)頁(yè)應(yīng)用,token可以存于localStorage或者sessionStorage里面,然后每發(fā)ajax請(qǐng)求的時(shí)候,都把token拿出來(lái)放到ajax請(qǐng)求的header里即可。不過(guò)如果是非接口的請(qǐng)求,比如直接通過(guò)點(diǎn)擊鏈接請(qǐng)求一個(gè)頁(yè)面這種,是無(wú)法自動(dòng)帶上token的。所以這種方式也僅限于走純接口的web應(yīng)用。
這種方式用在web應(yīng)用里也有跨域的問(wèn)題,比如應(yīng)用如果部署在a.com,api服務(wù)部署在b.com,從a.com里面發(fā)出ajax請(qǐng)求到b.com,默認(rèn)情況下是會(huì)報(bào)跨域錯(cuò)誤的,這種問(wèn)題可以用CORS(跨域資源共享)的方式來(lái)快速解決,相關(guān)細(xì)節(jié)可去閱讀前面給出的CORS文章詳細(xì)了解。
這種方式跟cookie-based的方式同樣都還有的一個(gè)問(wèn)題就是ticket或者token刷新的問(wèn)題。有的產(chǎn)品里面,你肯定不希望用戶登錄后,操作了半個(gè)小時(shí),結(jié)果ticket或者token到了過(guò)期時(shí)間,然后用戶又得去重新登錄的情況出現(xiàn)。這個(gè)時(shí)候就得考慮ticket或token的自動(dòng)刷新的問(wèn)題,簡(jiǎn)單來(lái)說(shuō),可以在驗(yàn)證ticket或token有效之后,自動(dòng)把ticket或token的失效時(shí)間延長(zhǎng),然后把它再返回給客戶端;客戶端如果檢測(cè)到服務(wù)器有返回新的ticket或token,就替換原來(lái)的ticket或token。
在web應(yīng)用里面,會(huì)話管理的安全性始終是最重要的安全問(wèn)題,這個(gè)對(duì)用戶的影響極大。
首先從會(huì)話管理憑證來(lái)說(shuō),第一種方式的會(huì)話憑證僅僅是一個(gè)session id,所以只要這個(gè)session id足夠隨機(jī),而不是一個(gè)自增的數(shù)字id值,那么其它人就不可能輕易地冒充別人的session id進(jìn)行操作;第二種方式的憑證(ticket)以及第三種方式的憑證(token)都是一個(gè)在服務(wù)端做了數(shù)字簽名,和加密處理的串,所以只要密鑰不泄露,別人也無(wú)法輕易地拿到這個(gè)串中的有效信息并對(duì)它進(jìn)行篡改??傊@三種會(huì)話管理方式的憑證本身是比較安全的。
然后從客戶端和服務(wù)端的http過(guò)程來(lái)說(shuō),當(dāng)別人截獲到客戶端請(qǐng)求中的會(huì)話憑證,就能拿這個(gè)憑證冒充原用戶,做一些非法操作,而服務(wù)器也認(rèn)不出來(lái)。這種安全問(wèn)題,可以簡(jiǎn)單采用https來(lái)解決,雖然可能還有http劫持這種更高程度的威脅存在,但是我們從代碼能做的防范,確實(shí)也就是這個(gè)層次了。
最后的安全問(wèn)題就是CSRF(跨站請(qǐng)求偽造)。這個(gè)跟代碼有很大關(guān)系,本質(zhì)上它就是代碼的漏洞,只不過(guò)一般情況下這些漏洞,作為開發(fā)人員都不容易發(fā)現(xiàn),只有那些一門心思想搞些事情的人才會(huì)專門去找這些漏洞,所以這種問(wèn)題的防范更多地還是依賴于開發(fā)人員對(duì)這種攻擊方式的了解,包括常見的攻擊形式和應(yīng)對(duì)方法。不管憑證信息本身多么安全,別人利用CSRF,就能拿到別人的憑證,然后用它冒充別人進(jìn)行非法操作,所以有時(shí)間還真得多去了解下它的相關(guān)資料才行。舉例來(lái)說(shuō),假如我們把憑證直接放到url后面進(jìn)行傳遞,就有可能成為一個(gè)CSRF的漏洞:當(dāng)惡意用戶在我們的應(yīng)用內(nèi)上傳了1張引用了他自己網(wǎng)站的圖片,當(dāng)正常的用戶登錄之后訪問(wèn)的頁(yè)面里面包含這個(gè)圖片的時(shí)候,由于這個(gè)圖片加載的時(shí)候會(huì)向惡意網(wǎng)站發(fā)送get請(qǐng)求;當(dāng)惡意網(wǎng)站收到請(qǐng)求的時(shí)候,就會(huì)從這個(gè)請(qǐng)求的Reffer header里面看到包含這個(gè)圖片的頁(yè)面地址,而這個(gè)地址正好包含了正常用戶的會(huì)話憑證;于是惡意用戶就拿到了正常用戶的憑證;只要這個(gè)憑證還沒(méi)失效,他就能用它冒充用戶進(jìn)行非法操作。
前面這三種方式,各自有各自的優(yōu)點(diǎn)及使用場(chǎng)景,我覺(jué)得沒(méi)有哪個(gè)是最好的,做項(xiàng)目的時(shí)候,根據(jù)項(xiàng)目將來(lái)的擴(kuò)展情況和架構(gòu)情況,才能決定用哪個(gè)是最合適的。本文的目的也就是想介紹這幾種方式的原理,以便掌握web應(yīng)用中登錄驗(yàn)證的關(guān)鍵因素。
作為一個(gè)前端開發(fā)人員,本文雖然介紹了3種會(huì)話管理的方式,但是與前端關(guān)系最緊密的還是第三種方式,畢竟現(xiàn)在前端開發(fā)SPA應(yīng)用以及hybrid應(yīng)用已經(jīng)非常流行了,所以掌握好這個(gè)方式的認(rèn)證過(guò)程和使用方式,對(duì)前端來(lái)說(shuō),顯然是很有幫助的。好在這個(gè)方式的技術(shù)其實(shí)早就有很多實(shí)現(xiàn)了,而且還有現(xiàn)成的標(biāo)準(zhǔn)可用,這個(gè)標(biāo)準(zhǔn)就是JWT(json-web-token)。
JWT本身并沒(méi)有做任何技術(shù)實(shí)現(xiàn),它只是定義了token-based的管理方式該如何實(shí)現(xiàn),它規(guī)定了token的應(yīng)該包含的標(biāo)準(zhǔn)內(nèi)容以及token的生成過(guò)程和方法。目前實(shí)現(xiàn)了這個(gè)標(biāo)準(zhǔn)的技術(shù)已經(jīng)有非常多:
更多可參閱:https://jwt.io/#libraries-io
為了對(duì)第三種會(huì)話管理方式的實(shí)現(xiàn)有個(gè)更全面的認(rèn)識(shí),我選擇用express和上面眾多JWT實(shí)現(xiàn)中的jsonwebtoken來(lái)研究,相關(guān)內(nèi)容我會(huì)在下一篇博客詳細(xì)介紹。本文內(nèi)容到此結(jié)束,謝謝閱讀,歡迎關(guān)注下一篇博客的內(nèi)容。
聯(lián)系客服