日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

python实现后台系统的JWT认证

發(fā)布時(shí)間:2025/5/22 python 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python实现后台系统的JWT认证 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

介紹一種適用于restful+json的API認(rèn)證方法,這個(gè)方法是基于jwt,并且加入了一些從oauth2.0借鑒的改良。

1. 常見(jiàn)的幾種實(shí)現(xiàn)認(rèn)證的方法

首先要明白,認(rèn)證和鑒權(quán)是不同的。認(rèn)證是判定用戶的合法性,鑒權(quán)是判定用戶的權(quán)限級(jí)別是否可執(zhí)行后續(xù)操作。這里所講的僅含認(rèn)證。認(rèn)證有幾種方法:

?

1.1 basic auth

這是http協(xié)議中所帶帶基本認(rèn)證,是一種簡(jiǎn)單為上的認(rèn)證方式。原理是在每個(gè)請(qǐng)求的header中添加用戶名和密碼的字符串(格式為“username:password”,用base64編碼)。

這種方式相當(dāng)于將“用戶名:密碼”綁定為一個(gè)開(kāi)放式證書(shū),這會(huì)有幾個(gè)問(wèn)題:

  • 每次請(qǐng)求都需要用戶名密碼,如果此連接未使用SSL/TLS,或加密被破解,用戶名密碼基本就暴露了;
  • 無(wú)法注銷用戶的登錄狀態(tài);
  • 證書(shū)不會(huì)過(guò)期,除非修改密碼。

總體來(lái)說(shuō),這種方法的特點(diǎn)就是,簡(jiǎn)單但不安全。

?

1.2cookie

將認(rèn)證的結(jié)果存在客戶端的cookie中,通過(guò)檢查cookie中的身份信息來(lái)作為認(rèn)證結(jié)果。
這種方式的特點(diǎn)是便捷,且只需要一次認(rèn)證,多次可用;也可以注銷登錄狀態(tài)和設(shè)置過(guò)期時(shí)間;甚至也有辦法(比如設(shè)置httpOnly)來(lái)避免XSS攻擊。

但它的缺點(diǎn)十分明顯,使用cookie那便是有狀態(tài)的服務(wù)了。

?

1.3 token

JWT協(xié)議似乎已經(jīng)應(yīng)用十分廣泛,JSON Web Token——一種基于token的json格式web認(rèn)證方法。

基本的原理是,第一次認(rèn)證通過(guò)用戶名密碼,服務(wù)端簽發(fā)一個(gè)json格式的token。后續(xù)客戶端的請(qǐng)求都攜帶這個(gè)token,服務(wù)端僅需要解析這個(gè)token,來(lái)判別客戶端的身份和合法性。

而JWT協(xié)議僅僅規(guī)定了這個(gè)協(xié)議的格式(<a href=”https://tools.ietf.org/heml/rfc7519”>RFC7519</a>),它的序列生成方法在JWS協(xié)議中描述(https://tools.ietf.org/html/rfc7515),分為三個(gè)部分:

1.3.1 header頭部:

  • 聲明類型,這里是jwt

  • 聲明加密的算法 通常直接使用 HMAC SHA256

一種常見(jiàn)的頭部是這樣的:

  • {
  • ‘typ’: ‘JWT’,
  • ‘a(chǎn)lg’: ‘HS256’
  • }
    • 1

    再將其進(jìn)行base64編碼。

    1.3.2 payload載荷:

    payload是放置實(shí)際有效使用信息的地方。JWT定義了幾種內(nèi)容,包括:

    • 標(biāo)準(zhǔn)中注冊(cè)的聲明,如簽發(fā)者,接收者,有效時(shí)間(exp),時(shí)間戳(iat,issued at)等;為官方建議但非必須
    • 公共聲明
    • 私有聲明

    一個(gè)常見(jiàn)的payload是這樣的:

  • {'user_id': 123456,
  • 'user_role': admin,
  • 'iat': 1467255177}
    • 1

    事實(shí)上,payload中的內(nèi)容是自由的,按照自己開(kāi)發(fā)的需要加入。

    Ps. 有個(gè)小問(wèn)題。使用itsdangerous包的TimedJSONWebSignatureSerializer進(jìn)行token序列生成的結(jié)果,exp是在頭部里的。這里似乎違背了jwt的協(xié)議規(guī)則。

    1.3.3 signature

    存儲(chǔ)了序列化的secreate key和salt key。這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。

    2. 認(rèn)證需求

    目標(biāo)場(chǎng)景是一個(gè)前后端分離的后端系統(tǒng),用于運(yùn)維工作,雖在內(nèi)網(wǎng)使用,也有一定的保密性要求。

    • API為restful+json的無(wú)狀態(tài)接口,要求認(rèn)證也是相同模式
    • 可橫向擴(kuò)展
    • 較低數(shù)據(jù)庫(kù)壓力
    • 證書(shū)可注銷
    • 證書(shū)可自動(dòng)延期

    選擇JWT。

    ?

    3. JWT實(shí)現(xiàn)

    2.1 如何生成token

    這里使用python模塊itsdangerous,這個(gè)模塊能做很多編碼工作,其中一個(gè)是實(shí)現(xiàn)JWS的token序列。
    genTokenSeq這個(gè)函數(shù)用于生成token。其中使用的是TimedJSONWebSignatureSerializer進(jìn)行序列的生成,這里secret_key密鑰、salt鹽值從配置文件中讀取,當(dāng)然也可以直接寫(xiě)死在這里。expires_in是超時(shí)時(shí)間間隔,這個(gè)間隔以秒記,可以直接在這里設(shè)置,我選擇將其設(shè)為方法的形參(因?yàn)檫@個(gè)函數(shù)也用在了解決下提到的問(wèn)題2)。

  • # serializer for JWT
  • from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
  • """
  • token is generated as the JWT protocol.
  • JSON Web Tokens(JWT) are an open, industry standard RFC 7519 method
  • """
  • def genTokenSeq(self, expires):
  • s = Serializer(
  • secret_key=app.config['SECRET_KEY'],
  • salt=app.config['AUTH_SALT'],
  • expires_in=expires)
  • timestamp = time.time()
  • return s.dumps(
  • {'user_id': self.user_id,
  • 'user_role': self.role_id,
  • 'iat': timestamp})
  • # The token contains userid, user role and the token generation time.
  • # u can add sth more inside, if needed.
  • # 'iat' means 'issued at'. claimed in JWT.
    • 1

    使用這個(gè)Serializer可以幫我們處理好header、signature的問(wèn)題。我們只需要用s.dumps將payload的內(nèi)容寫(xiě)進(jìn)來(lái)。這里我準(zhǔn)備在每個(gè)token中寫(xiě)入三個(gè)值:用戶id、用戶角色id和當(dāng)前時(shí)間(‘iat’是JWT標(biāo)準(zhǔn)注冊(cè)聲明中的一項(xiàng))。

    假設(shè)我所寫(xiě)入的信息是

  • {
  • "iat": 1467271277.131803,
  • "user_id": "46501228343b11e6aaa6a45e60ed5ed5f973ba0fcf783bb8ade34c7b492d9e55",
  • "user_role": 3
  • }
    • 1

    采用以上的方法所生成的token為

    eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NzM0MTQ3NCwiaWF0IjoxNDY3MzM3ODc0fQ.eyJpYXQiOjE0NjczMzc4NzQuNzE3MDYzLCJ1c2VyX2lkIjoiNDY1MDEyMjgzNDNiMTFlNmFhYTZhNDVlNjBlZDVlZDVmOTczYmEwZmNmNzgzYmI4YWRlMzRjN2I0OTJkOWU1NSIsInVzZXJfcm9sZSI6M30.23QD0OwLjdioKu5BgbaH2gHT2GoMz90n8VZcpvdyp7U
    • 1
    • 2

    它是由“header.payload.signature”構(gòu)成的。

    ?

    3.2 如何解析token

    解析需要使用到同樣的serializer,配置一樣的secret key和salt,使用loads方法來(lái)解析token。itsdangerous提供了各種異常處理類,用起來(lái)也很方便:

    如果是SignatureExpired,則可以直接返回過(guò)期;
    如果是BadSignature,則代表了所有其他簽名錯(cuò)誤的情況,于是又分為:

    • 能讀取到payload:那么這個(gè)消息是一個(gè)內(nèi)容被篡改、消息體加密過(guò)程正確的消息,secret key和salt很可能泄露了;
    • 不能讀取到payload: 消息體直接被篡改,secret key和salt應(yīng)該仍然安全。

    以上內(nèi)容寫(xiě)成一個(gè)函數(shù),用于驗(yàn)證用戶token。如果實(shí)現(xiàn)在python flask,可以考慮將此函數(shù)改為一個(gè)decorator修飾漆,將修飾器@到所有需要驗(yàn)證token的方法前面,則代碼可以更加優(yōu)雅。

  • # serializer for JWT
  • from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
  • # exceptions for JWT
  • from itsdangerous import SignatureExpired, BadSignature, BadData
  • # Class xxx
  • # after definition of your class, here goes the auth method:
  • def tokenAuth(token):
  • # token decoding
  • s = Serializer(
  • secret_key=api.app.config['SECRET_KEY'],
  • salt=api.app.config['AUTH_SALT'])
  • try:
  • data = s.loads(token)
  • # token decoding faild
  • # if it happend a plenty of times, there might be someone
  • # trying to attact your server, so it should be a warning.
  • except SignatureExpired:
  • msg = 'token expired'
  • app.logger.warning(msg)
  • return [None, None, msg]
  • except BadSignature, e:
  • encoded_payload = e.payload
  • if encoded_payload is not None:
  • try:
  • s.load_payload(encoded_payload)
  • except BadData:
  • # the token is tampered.
  • msg = 'token tampered'
  • app.logger.warning(msg)
  • return [None, None, msg]
  • msg = 'badSignature of token'
  • app.logger.warning(msg)
  • return [None, None, msg]
  • except:
  • msg = 'wrong token with unknown reason'
  • app.logger.warning(msg)
  • return [None, None, msg]
  • if ('user_id' not in data) or ('user_role' not in data):
  • msg = 'illegal payload inside'
  • app.logger.warning(msg)
  • return [None, None, msg]
  • msg = 'user(' + data['user_id'] + ') logged in by token.'
  • # app.logger.info(msg)
  • userId = data['user_id']
  • roleId = data['user_role']
  • return [userId, roleId, msg]
    • 1

    檢查和判定的機(jī)制如下:

  • 使用加密的類,再用來(lái)解密(用上之前的密鑰和鹽值),得到結(jié)果存入data;
  • 如果捕獲到SignatureExpired異常,則代表根據(jù)token中的expired設(shè)置,token已經(jīng)超時(shí)失效,返回‘token expired’;
  • 如果是其他BadSignature異常,又要分為:
    3.1 如果payload還完整,則解析payload,如果捕獲BadData異常,則代表token已經(jīng)被篡改,返回‘token tampered’;
    3.2 如果payload不完整,直接返回‘badSignature of token’;
  • 如果以上異常都不對(duì),那只能返回未知異常‘wrong token with unknown reason’;
  • 最后,如果data能正常解析,則將payload中的數(shù)據(jù)取出來(lái),驗(yàn)證payload中是否有合法信息(這里是user_id和user_role鍵值的json數(shù)據(jù)),如果數(shù)據(jù)不合法,則返回‘illegal payload inside’。一旦出現(xiàn)這種情況,則代表密鑰和鹽值泄露的可能性很大。
  • 4. 優(yōu)化

    上述的方法可以做到基本的JWT認(rèn)證,但在實(shí)際開(kāi)發(fā)過(guò)程中還有其他問(wèn)題:

    token在生成之后,是靠expire使其過(guò)期失效的。簽發(fā)之后的token,是無(wú)法收回修改的,因此涉及token的有效期的更改是個(gè)難題,它體現(xiàn)在以下兩個(gè)問(wèn)題:

    • 問(wèn)題1.用戶登出
    • 問(wèn)題2.token自動(dòng)延期

    如何解決更改token有效期的問(wèn)題,網(wǎng)上看到很多討論,主要集中在以下內(nèi)容:

  • JWT是一次性認(rèn)證完畢加載信息到token里的,token的信息內(nèi)含過(guò)期信息。過(guò)期時(shí)間過(guò)長(zhǎng)則被重放攻擊的風(fēng)險(xiǎn)太大,而過(guò)期時(shí)間太短則請(qǐng)求端體驗(yàn)太差(動(dòng)不動(dòng)就要重新登錄)
  • 把token存進(jìn)庫(kù)里,很自然能想到的是把每個(gè)token存庫(kù),設(shè)置一個(gè)valid字段,一旦注銷了就valid=0;設(shè)置有效期字段,想要延期就增加有效期時(shí)間。openstack keystone就是這么做的。這個(gè)做法雖方便,但對(duì)數(shù)據(jù)庫(kù)的壓力較大,甚至在訪問(wèn)量較大,簽發(fā)token較多的情況下,是對(duì)數(shù)據(jù)庫(kù)的一個(gè)挑戰(zhàn)。況且這也有悖于JWT的初衷。
  • 為了使用戶不需要經(jīng)常重新登錄,客戶端將用戶名密碼保存起來(lái)(cookie),然后使用用戶名密碼驗(yàn)證,但那還得考慮防御CSRF攻擊的問(wèn)題。
  • 這里,筆者借鑒了第三方認(rèn)證協(xié)議Oauth2.0(<a href=”https://tools.ietf.org/html/rfc6749”>RFC6749</a>),它采取了另一種方法:refresh token,一個(gè)用于更新令牌的令牌。在用戶首次認(rèn)證后,簽發(fā)兩個(gè)token:

    • 一個(gè)為access token,用于用戶后續(xù)的各個(gè)請(qǐng)求中攜帶的認(rèn)證信息
    • 另一個(gè)是refresh token,為access token過(guò)期后,用于申請(qǐng)一個(gè)新的access token。

    由此可以給兩類不同token設(shè)置不同的有效期,例如給access token僅1小時(shí)的有效時(shí)間,而refresh token則可以是一個(gè)月。api的登出通過(guò)access token的過(guò)期來(lái)實(shí)現(xiàn)(前端則可直接拋棄此token實(shí)現(xiàn)登出),在refresh token的存續(xù)期內(nèi),訪問(wèn)api時(shí)可執(zhí)refresh token申請(qǐng)新的access token(前端可存此refresh token,access token過(guò)其實(shí)進(jìn)行更新,達(dá)到自動(dòng)延期的效果)。

    refresh token不可再延期,過(guò)期需重新使用用戶名密碼登錄。

    這種方式的理念在于,將證書(shū)分為三種級(jí)別:

    • access token 短期證書(shū),用于最終鑒權(quán)
    • refresh token 較長(zhǎng)期的證書(shū),用于產(chǎn)生短期證書(shū),不可直接用于服務(wù)請(qǐng)求
    • 用戶名密碼 幾乎永久的證書(shū),用于產(chǎn)生長(zhǎng)期證書(shū)和短期證書(shū),不可直接用于服務(wù)請(qǐng)求

    通過(guò)這種方式,使證書(shū)功效和證書(shū)時(shí)效結(jié)合考慮。
    ps.前面提到創(chuàng)建token的時(shí)候?qū)xpire_in(jwt的推薦字段,超時(shí)時(shí)間間隔)作為函數(shù)的形參,是為了將此函數(shù)用于生成access token和refresh token,而兩者的expire_in時(shí)間是不同的。

    ?

    5. 總結(jié)一下

    我們做了一個(gè)JWT的認(rèn)證模塊:
    (access token在以下代碼中為’token’,refresh token在代碼中為’rftoken’)

    • 首次認(rèn)證

    client —–用戶名密碼———–> server

    client <——token、rftoken—– server

    • access token存續(xù)期內(nèi)的請(qǐng)求

    client ——請(qǐng)求(攜帶token)—-> server

    client <—–結(jié)果—————– server

    • access token超時(shí)

    client ——請(qǐng)求(攜帶token)—-> server

    client <—–msg:token expired— server

    • 重新申請(qǐng)access token

    client -請(qǐng)求新token(攜帶rftoken)-> server

    client <—–新token————– server

    • rftoken token超時(shí)

    client -請(qǐng)求新token(攜帶rftoken)-> server

    client <—-msg:rftoken expired— server

    如果設(shè)計(jì)一個(gè)針對(duì)此認(rèn)證的前端,需要:

      • 存儲(chǔ)access token、refresh token

      • 訪問(wèn)時(shí)攜帶access token,自動(dòng)檢查access token超時(shí),超時(shí)則使用refresh token更新access token;狀態(tài)延期用戶無(wú)感知

      • 用戶登出直接拋棄access token與refresh token

    其他參考文章:

    基于前后端分離的身份認(rèn)證方式——JWT

    初學(xué)Django:使用Python官方的hmac庫(kù)生成JWT

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/robinunix/p/9978599.html

    總結(jié)

    以上是生活随笔為你收集整理的python实现后台系统的JWT认证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。