python中JWT用戶認(rèn)證的實(shí)現(xiàn)
在前后端分離開發(fā)時(shí)為什么需要用戶認(rèn)證呢?原因是由于HTTP協(xié)定是不儲存狀態(tài)的(stateless),這意味著當(dāng)我們透過帳號密碼驗(yàn)證一個(gè)使用者時(shí),當(dāng)下一個(gè)request請求時(shí)它就把剛剛的資料忘了。于是我們的程序就不知道誰是誰,就要再驗(yàn)證一次。所以為了保證系統(tǒng)安全,我們就需要驗(yàn)證用戶否處于登錄狀態(tài)。
一、傳統(tǒng)方式
前后端分離通過Restful API進(jìn)行數(shù)據(jù)交互時(shí),如何驗(yàn)證用戶的登錄信息及權(quán)限。在原來的項(xiàng)目中,使用的是最傳統(tǒng)也是最簡單的方式,前端登錄,后端根據(jù)用戶信息生成一個(gè)token,并保存這個(gè)token 和對應(yīng)的用戶id到數(shù)據(jù)庫或Session中,接著把token 傳給用戶,存入瀏覽器 cookie,之后瀏覽器請求帶上這個(gè)cookie,后端根據(jù)這個(gè)cookie值來查詢用戶,驗(yàn)證是否過期。
但這樣做問題就很多,如果我們的頁面出現(xiàn)了 XSS 漏洞,由于 cookie 可以被 JavaScript 讀取,XSS 漏洞會導(dǎo)致用戶 token 泄露,而作為后端識別用戶的標(biāo)識,cookie 的泄露意味著用戶信息不再安全。盡管我們通過轉(zhuǎn)義輸出內(nèi)容,使用 CDN 等可以盡量避免 XSS 注入,但誰也不能保證在大型的項(xiàng)目中不會出現(xiàn)這個(gè)問題。
在設(shè)置 cookie 的時(shí)候,其實(shí)你還可以設(shè)置 httpOnly 以及 secure項(xiàng)。設(shè)置 httpOnly后 cookie 將不能被 JS 讀取,瀏覽器會自動(dòng)的把它加在請求的 header 當(dāng)中,設(shè)置 secure的話,cookie 就只允許通過 HTTPS 傳輸。secure 選項(xiàng)可以過濾掉一些使用 HTTP 協(xié)議的 XSS 注入,但并不能完全阻止。
httpOnly 選項(xiàng)使得 JS 不能讀取到 cookie,那么 XSS 注入的問題也基本不用擔(dān)心了。但設(shè)置 httpOnly就帶來了另一個(gè)問題,就是很容易的被 XSRF,即跨站請求偽造。當(dāng)你瀏覽器開著這個(gè)頁面的時(shí)候,另一個(gè)頁面可以很容易的跨站請求這個(gè)頁面的內(nèi)容。因?yàn)?cookie 默認(rèn)被發(fā)了出去。
另外,如果將驗(yàn)證信息保存在數(shù)據(jù)庫中,后端每次都需要根據(jù)token查出用戶id,這就增加了數(shù)據(jù)庫的查詢和存儲開銷。若把驗(yàn)證信息保存在session中,有加大了服務(wù)器端的存儲壓力。那我們可不可以不要服務(wù)器去查詢呢?如果我們生成token遵循一定的規(guī)律,比如我們使用對稱加密算法來加密用戶id形成token,那么服務(wù)端以后其實(shí)只要解密該token就可以知道用戶的id是什么了。不過呢,我只是舉個(gè)例子而已,要是真這么做,只要你的對稱加密算法泄露了,其他人可以通過這種加密方式進(jìn)行偽造token,那么所有用戶信息都不再安全了。恩,那用非對稱加密算法來做呢,其實(shí)現(xiàn)在有個(gè)規(guī)范就是這樣做的,就是我們接下來要介紹的 JWT。
二、Json Web Token(JWT)
WT 是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種用于簡潔,自包含的用于通信雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公鑰密鑰對進(jìn)行簽名。它具備兩個(gè)特點(diǎn):
簡潔(Compact)可以通過URL, POST 參數(shù)或者在 HTTP header 發(fā)送,因?yàn)閿?shù)據(jù)量小,傳輸速度快
自包含(Self-contained)負(fù)載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫
JWT 組成
Header 頭部
頭部包含了兩部分,token 類型和采用的加密算法
{ 'alg': 'HS256', 'typ': 'JWT'}
它會使用 Base64 編碼組成 JWT 結(jié)構(gòu)的第一部分,如果你使用Node.js,可以用Node.js的包base64url來得到這個(gè)字符串。------------ Base64是一種編碼,也就是說,它是可以被翻譯回原來的樣子來的。它并不是一種加密過程。
Payload 負(fù)載
負(fù)載就是存放有效信息的地方。這些有效信息包含三個(gè)部分:----標(biāo)準(zhǔn)中注冊聲明----公共的聲明----私有的聲明
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷堋?/p>
私有的聲明:
私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對稱解密的,意味著該部分信息可以歸類為明文信息
{ 'iss': 'lion1ou JWT', 'iat': 1441593502, 'exp': 1441594722, 'aud': 'www.example.com', 'sub': 'lion1ou@163.com'}// 包括需要傳遞的用戶信息;{ 'iss': 'Online JWT Builder', 'iat': 1416797419, 'exp': 1448333419, 'aud': 'www.gusibi.com', 'sub': 'uid', 'nickname': 'goodspeed', 'username': 'goodspeed', 'scopes': [ 'admin', 'user' ] } iss: 該JWT的簽發(fā)者,是否使用是可選的; sub: 該JWT所面向的用戶,是否使用是可選的; aud: 接收該JWT的一方,是否使用是可選的; exp(expires): 什么時(shí)候過期,這里是一個(gè)Unix時(shí)間戳,是否使用是可選的; iat(issued at): 在什么時(shí)候簽發(fā)的(UNIX時(shí)間),是否使用是可選的;
其他還有:
nbf (Not Before):如果當(dāng)前時(shí)間在nbf里的時(shí)間之前,則Token不被接受;一般都會留一些余地,比如幾分鐘;,是否使用是可選的; jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊。 同樣的,它會使用 Base64 編碼組成 JWT 結(jié)構(gòu)的第二部分。 Signature 簽名前面兩部分都是使用 Base64 進(jìn)行編碼的,即前端可以解開知道里面的信息。Signature 需要使用編碼后的 header 和 payload 以及我們提供的一個(gè)密鑰,然后使用 header 中指定的簽名算法(HS256)進(jìn)行簽名。簽名的作用是保證 JWT 沒有被篡改過。
// 根據(jù)alg算法與私有秘鑰進(jìn)行加密得到的簽名字串;// 這一段是最重要的敏感信息,只能在服務(wù)端解密;HMACSHA256( base64UrlEncode(header) + '.' + base64UrlEncode(payload), SECREATE_KEY)
三個(gè)部分通過.連接在一起就是我們的 JWT 了,它可能長這個(gè)樣子,長度貌似和你的加密算法和私鑰有關(guān)系。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
其實(shí)到這一步可能就有人會想了,HTTP 請求總會帶上 token,這樣這個(gè) token 傳來傳去占用不必要的帶寬啊。如果你這么想了,那你可以去了解下 HTTP2,HTTP2 對頭部進(jìn)行了壓縮,相信也解決了這個(gè)問題。
簽名的目的
最后一步簽名的過程,實(shí)際上是對頭部以及負(fù)載內(nèi)容進(jìn)行簽名,防止內(nèi)容被竄改。如果有人對頭部以及負(fù)載的內(nèi)容解碼之后進(jìn)行修改,再進(jìn)行編碼,最后加上之前的簽名組合形成新的JWT的話,那么服務(wù)器端會判斷出新的頭部和負(fù)載形成的簽名和JWT附帶上的簽名是不一樣的。如果要對新的頭部和負(fù)載進(jìn)行簽名,在不知道服務(wù)器加密時(shí)用的密鑰的話,得出來的簽名也是不一樣的。
信息暴露
在這里大家一定會問一個(gè)問題:Base64是一種編碼,是可逆的,那么我的信息不就被暴露了嗎?
是的。所以,在JWT中,不應(yīng)該在負(fù)載里面加入任何敏感的數(shù)據(jù)。在上面的例子中,我們傳輸?shù)氖怯脩舻腢ser ID。這個(gè)值實(shí)際上不是什么敏感內(nèi)容,一般情況下被知道也是安全的。但是像密碼這樣的內(nèi)容就不能被放在JWT中了。如果將用戶的密碼放在了JWT中,那么懷有惡意的第三方通過Base64解碼就能很快地知道你的密碼了。
因此JWT適合用于向Web應(yīng)用傳遞一些非敏感信息。JWT還經(jīng)常用于設(shè)計(jì)用戶認(rèn)證和授權(quán)系統(tǒng),甚至實(shí)現(xiàn)Web應(yīng)用的單點(diǎn)登錄。
token 生成好之后,接下來就可以用token來和服務(wù)器進(jìn)行通訊了。
三、JWT 使用
下圖是client 使用 JWT 與server 交互過程:
1.這里在第三步我們得到 JWT 之后,需要將JWT存放在 client,之后的每次需要認(rèn)證的請求都要把JWT發(fā)送過來。(請求時(shí)可以放到 header 的 Authorization )首先,前端通過Web表單將自己的用戶名和密碼發(fā)送到后端的接口。這一過程一般是一個(gè)HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協(xié)議),從而避免敏感信息被嗅探。
2.后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負(fù)載),將其與頭部分別進(jìn)行Base64編碼拼接后簽名,形成一個(gè)JWT。形成的JWT就是一個(gè)形同lll.zzz.xxx的字符串。
3.后端將JWT字符串作為登錄成功的返回結(jié)果返回給前端。前端可以將返回的結(jié)果保存在localStorage或sessionStorage上,退出登錄時(shí)前端刪除保存的JWT即可。
4.前端在每次請求時(shí)將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
5.后端檢查是否存在,如存在驗(yàn)證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過期;檢查Token的接收方是否是自己(可選)。
6.驗(yàn)證通過后后端使用JWT中包含的用戶信息進(jìn)行其他邏輯操作,返回相應(yīng)結(jié)果。
四、JWT 使用場景
WT的主要優(yōu)勢在于使用無狀態(tài)、可擴(kuò)展的方式處理應(yīng)用中的用戶會話。服務(wù)端可以通過內(nèi)嵌的聲明信息,很容易地獲取用戶的會話信息,而不需要去訪問用戶或會話的數(shù)據(jù)庫。在一個(gè)分布式的面向服務(wù)的框架中,這一點(diǎn)非常有用。
但是,如果系統(tǒng)中需要使用黑名單實(shí)現(xiàn)長期有效的token刷新機(jī)制,這種無狀態(tài)的優(yōu)勢就不明顯了。
優(yōu)點(diǎn)快速開發(fā)不需要cookieJSON在移動(dòng)端的廣泛應(yīng)用不依賴于社交登錄相對簡單的概念理解
缺點(diǎn)Token有長度限制Token不能撤銷需要token有失效時(shí)間限制(exp)
五、和Session方式存儲id的差異
Session方式存儲用戶id的最大弊病在于Session是存儲在服務(wù)器端的,所以需要占用大量服務(wù)器內(nèi)存,對于較大型應(yīng)用而言可能還要保存許多的狀態(tài)。一般而言,大型應(yīng)用還需要借助一些KV數(shù)據(jù)庫和一系列緩存機(jī)制來實(shí)現(xiàn)Session的存儲。
而JWT方式將用戶狀態(tài)分散到了客戶端中,可以明顯減輕服務(wù)端的內(nèi)存壓力。除了用戶id之外,還可以存儲其他的和用戶相關(guān)的信息,例如該用戶是否是管理員、用戶所在的分組等。雖說JWT方式讓服務(wù)器有一些計(jì)算壓力(例如加密、編碼和解碼),但是這些壓力相比磁盤存儲而言可能就不算什么了。具體是否采用,需要在不同場景下用數(shù)據(jù)說話。
單點(diǎn)登錄
Session方式來存儲用戶id,一開始用戶的Session只會存儲在一臺服務(wù)器上。對于有多個(gè)子域名的站點(diǎn),每個(gè)子域名至少會對應(yīng)一臺不同的服務(wù)器,例如:www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com。所以如果要實(shí)現(xiàn)在login.taobao.com登錄后,在其他的子域名下依然可以取到Session,這要求我們在多臺服務(wù)器上同步Session。使用JWT的方式則沒有這個(gè)問題的存在,因?yàn)橛脩舻臓顟B(tài)已經(jīng)被傳送到了客戶端。
六、總結(jié)
JWT的主要作用在于:(一)可附帶用戶信息,后端直接通過JWT獲取相關(guān)信息。(二)使用本地保存,通過HTTP Header中的Authorization位提交驗(yàn)證。
七、附加,python使用JWT
python 中djagno rest framework要使用jwt,可以使用以下這個(gè)模塊:githubs文檔有使用說明https://github.com/GetBlimp/django-rest-framework-jwt
pip install djangorestframework-jwt
不是使用django的話,我們可以使用 pyjwt:https://github.com/jpadilla/pyjwt/使用比較方便,下邊是我在應(yīng)用中使用的例子:
import jwtimport time# 使用 sanic 作為restful api 框架 def create_token(request): grant_type = request.json.get(’grant_type’) username = request.json[’username’] password = request.json[’password’] if grant_type == ’password’: account = verify_password(username, password) elif grant_type == ’wxapp’: account = verify_wxapp(username, password) if not account: return {} payload = { 'iss': 'gusibi.com', 'iat': int(time.time()), 'exp': int(time.time()) + 86400 * 7, 'aud': 'www.gusibi.com', 'sub': account[’_id’], 'username': account[’username’], 'scopes': [’open’] } token = jwt.encode(payload, ’secret’, algorithm=’HS256’) return True, {’access_token’: token, ’account_id’: account[’_id’]} def verify_bearer_token(token): # 如果在生成token的時(shí)候使用了aud參數(shù),那么校驗(yàn)的時(shí)候也需要添加此參數(shù) payload = jwt.decode(token, ’secret’, audience=’www.gusibi.com’, algorithms=[’HS256’]) if payload: return True, token return False, token
到此這篇關(guān)于python中JWT用戶認(rèn)證的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)python JWT用戶認(rèn)證內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. .Net Core和RabbitMQ限制循環(huán)消費(fèi)的方法2. jsp網(wǎng)頁實(shí)現(xiàn)貪吃蛇小游戲3. asp(vbs)Rs.Open和Conn.Execute的詳解和區(qū)別及&H0001的說明4. ASP.NET MVC遍歷驗(yàn)證ModelState的錯(cuò)誤信息5. 用css截取字符的幾種方法詳解(css排版隱藏溢出文本)6. ASP 信息提示函數(shù)并作返回或者轉(zhuǎn)向7. asp中response.write("中文")或者js中文亂碼問題8. PHP設(shè)計(jì)模式中工廠模式深入詳解9. CSS hack用法案例詳解10. 將properties文件的配置設(shè)置為整個(gè)Web應(yīng)用的全局變量實(shí)現(xiàn)方法
