官網介紹:
JSON Web Token(JWT)是一個開放標准(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間安全地將信息作為JSON對象傳輸。由於此信息是經過數字簽名的,因此可以被驗證和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公用/專用密鑰對對JWT進行簽名。
盡管可以對JWT進行加密以在各方之間提供保密性,但我們將重點關注已簽名的令牌。簽名的令牌可以驗證其中包含的聲明的完整性,而加密的令牌則將這些聲明隱藏在其他方的面前。當使用公鑰/私鑰對對令牌進行簽名時,簽名還證明只有持有私鑰的一方才是對其進行簽名的一方。
emmmm.......balabala一堆文字,那麼我們來簡單總結下:
JWT是一個JSON信息傳輸的開放標准,它可以使用密鑰對信息進行數字簽名,以確保信息是可驗證和可信任的。
JWT由三部分構成:header(頭部)、payload(載荷)和signature(簽名)。 以緊湊的形式由這三部分組成,由“.“分隔。
因此,JWT通常如下所示。
xxxxx.yyyyy.zzzzz
讓我們把這串奇奇怪怪的東西分解開來:
header通常由兩部分組成:令牌的類型(即JWT)和所使用的簽名算法,例如HMAC SHA256或RSA等等。
例如:
{ "alg": "HS256", "typ": "JWT" }
顯而易見,這貨是一個json數據,然後這貨會被Base64編碼形成JWT的第一部分,也就是xxxxx.yyyyy.zzzzz
中的xxxxxx。
這貨是JWT的第二部分,叫載荷(負載),內容也是一個json對象,它是存放有效信息的地方,它可以存放JWT提供的現成字段 :
iss: 該JWT的簽發者。
sub: 該JWT所面向的用戶。
aud: 接收該JWT的一方。
exp(expires): 什麼時候過期,這裡是一個Unix時間戳。
iat(issued at): 在什麼時候簽發的。
舉個例子:
{
"iss": "www.baidu.com",
"sub": "you",
"aud": "me",
"name": "456",
"admin": true,
"iat": 1584091337,
"exp": 1784091337,
}
這貨同樣會被Base64編碼,然後形成JWT的第二部分,也就是xxxxx.yyyyy.zzzzz
中的yyyyyy。
這是JWT的第三部分,叫做簽名,此部分用於防止JWT內容被篡改。將上面的兩個編碼後的字符串都用英文句號.連接在一起(頭部在前),就形成了
xxxxxx.yyyyyy
然後再使用header中聲明簽名算法進行簽名。 如果要使用HMAC SHA256算法,則將通過以下方式創建簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
當然,在加密的時候,我們還需要提供一個密鑰(secret),我們可以自己隨意指定。這樣就形成了JWT的第三部分,也就是xxxxx.yyyyy.zzzzz
中的zzzzzz。
最後,我們把這三個部分拼在一起,就形成了一個完整的JWT。
下面展示了一個完整的JWT,它先對header和payload進行編碼,最後用一個密鑰形成了簽名。
如果我們想試驗一下的話,可以在JWT的官網進行debugger。貼一下官網:https://jwt.io/
以下是JSON Web Token 有用的一些情況:
授權:這是使用JWT的最常見方案。一旦用戶登錄,每個後續請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務和資源。單一登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小並且可以在不同的域中輕松使用。
信息交換:JSON Web Token是在各方之間安全地傳輸信息的好方法。因為可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是他們所說的人。此外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否遭到篡改。
那麼,有人就會說了,道理我都懂,我應該怎樣去實現呢?莫慌。。
接下來我會用python實現JWT,不想拉仇恨,但是,python大法好啊。。。。
在前後端分離的項目中,我們需要與前端約定一種身份認證機制。當用戶登錄的時候,後端會生成token,然後返回給前端,前端需要將token拿到並按照一定規則放到header中,在下一次請求的時候一並發送給後端,後端進行token身份校驗。
這裡我們約定前端請求後端服務時需要添加頭信息Authorization ,內容為token。
我用的是fastapi web框架,搭建項目非常快。
from datetime import timedelta, datetime
import jwt
from fastapi import FastAPI, HTTPException, Depends
from starlette.status import HTTP_401_UNAUTHORIZED
from starlette.requests import Request
app = FastAPI()
SECRET_KEY = "sdifhgsiasfjaofhslio" # JWY簽名所使用的密鑰,是私密的,只在服務端保存
ALGORITHM = "HS256" # 加密算法,我這裡使用的是HS256
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.post("/create_token")
def create_token(username,password):
if username == "123" and password == "123":
access_token_expires = timedelta(minutes=60)
expire = datetime.utcnow() + access_token_expires
payload = {
"sub": username,
"exp": expire
}
# 生成Token,返回給前端
access_token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return {"access_token": access_token, "token_type": "bearer"}
else:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="username or password are not true",
headers={"WWW-Authenticate": "Bearer"}
)
def authorized_user(token):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
print(username)
if username == "123":
return username
except jwt.PyJWTError:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="認證失敗,無權查看",
headers={"WWW-Authenticate": "Bearer"},)
@app.get("/app")
def create_token(request: Request):
print(request.headers.get("host"), request.headers.get("Authorization"))
user = authorized_user(request.headers.get("Authorization")) # 驗證Token
if user:
return {"username": user,"detail": "JWT通過,查詢成功"}
這裡,由於現有的JWT庫已經幫我們封裝好了,我們可以使用JWT直接生成 token,不用手動base64加密和拼接。
測試一下:
啟動項目之後,我們打開http://127.0.0.1:8000/docs# ,就會看到以下我們編寫好的api:
首先,我們先驗證一下create_token接口
當我們輸入用戶名,密碼後,後端進行驗證,驗證成功後會返回給前端一個token,也就是JWT。當前端拿到這個token之後,下次在請求的時候就必須要帶上這個token了,因為前後端已經約定好了。接下來我們試一下:
認證失敗???
什麼原因導致的呢??讓我們點開檢查抓一下包看看:
恍然大悟,剛才我們說過,前後端事先約定好的,請求的header中一定要帶上token,在Authorization ,內容token。我們現在這個請求的header中並沒有帶上token,那這種debug模式下又是改不了請求header信息的,我們可以使用接口測試工具進行測試,我主推Postman!!!,讓我們來試一下:
至此,JWT介紹以及使用梳理完畢。