本文将会简单介绍什么是HTTP请求中的JWT鉴权以及如何在实际项目中使用JWT。
欢迎关注我的公众号NLP奇幻之旅 ,原创技术文章第一时间推送。
欢迎关注我的知识星球“自然语言处理奇幻之旅 ”,笔者正在努力构建自己的技术社区。
什么是JWT?
什么是HTTP请求鉴权?
在介绍HTTP请求中的JWT鉴权前,我们首先需要了解什么是HTTP请求中的鉴权。
在我们日常使用互联网服务时,常常离不开用户认证,这里面其实就是HTTP请求鉴权。
HTTP请求中的鉴权(Authentication/Authorization)是指:在客户端(如浏览器、App、API调用者)访问受保护资源时,服务端对其身份的确认与权限的验证。它主要分为两部分:认证 和授权 。所谓认证 ,指的是客户端需要提供自己的“身份凭证”,服务端根据凭证确认身份是否合法。而授权 ,指的是即使认证通过,服务端还要判断:你是否能访问这个接口或资源?
目前我们常用的鉴权有四种:
HTTP Basic Authentication
session-cookie
Token 验证
OAuth(开放授权)
本文将会介绍的JWT鉴权属于Token 验证。
JWT原理
JWT 的全称为JSON Web Token,其基本原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
1 2 3 4 5 { "姓名" : "张三" , "角色" : "管理员" , "到期时间" : "2018年7月1日0点0分" }
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。
JWT结构
实际上我们使用 JWT 形似如下的字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ
它由 . 隔开,分为三部分:Header
(头部)、Payload
(负载)、Signature
(签名)。
Header
部分经过解析后是一个 JSON 对象,用来描述 JWT 的元数据,一般结构如下:
1 2 3 4 { "alg" : "HS256" , "typ" : "JWT" }
alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256;typ属性表示 token 的类型(type)。将 JSON 对象使用 Base64URL 算法转成字符串。
Payload
部分经过解析后也是一个 JSON 对象,用来存放用户身份相关数据。例如:
1 2 3 4 5 6 { "sub" : "1234567890" , "name" : "George White" , "admin" : true , "iat" : 1516239022 }
将上面的 JSON 对象使用 Base64URL 算法转成字符串,Header部分和Payload部均采用这个算法实现。
官方给出的Payload部分的7个标准字段如下:
1 2 3 4 5 6 7 iss (issuer) :签发人exp (expiration time) :过期时间sub (subject) :主题aud (audience) :受众nbf (Not Before) :生效时间iat (Issued At) :签发时间jti (JWT ID) :编号
Signature
部分则是通过加盐加密得到的,加密由服务端实现,实现方式如下图:
简单来说,就是把 Header
和 Payload
部分进行 base64编码,用点拼接起来,得到结果,把这个结果用我们设置的密钥和算法进行加盐加密,就得到了 Signature
部分。
Python 实现 JWT
在Python语言中,有不少第三方模块提供了JWT的实现,我们以python-jose
模块为例,来演示如何使用JWT。
首先是使用该模块生成和验证JWT,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from jose import jwt payload = {"user_id" : "1234" , "user_name" : "Python" } secret_key = "jc2025" token = jwt.encode(payload, secret_key, algorithm='HS256' )print ("JWT Token:" , token) decoded_token = jwt.decode(token, secret_key, algorithms=['HS256' ])print ("Decoded Payload:" , decoded_token)
输出结果如下:
1 2 JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNCIsInVzZXJfbmFtZSI6IlB5dGhvbiJ9 .IzJ1AplGgQzPuOO2RRnx_PscdwL1efZw8FRa0bjpIKs Decoded Payload: {'user_id' : '1234' , 'user_name' : 'Python' }
有不少网站也提供了JWT的解析,比如网站 https://jwt.io/ ,这个网站提供的 JWT 认证功能简单实用。我们将上面的JWT Token在这个网站进行解析,如下图:
我们将JWT Token粘贴至左侧的ENCODE VALUE,右侧就能自动解析出 Header
和 Payload
部分,但 Signature
部分需要我们提供正确的秘钥(SECRET)才能验证。
我们在上面的JWT的生成和验证功能基础上,再引入一个超时机制,这也是 JWT 常见的使用方式。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import timefrom jose import jwtfrom datetime import datetime, timedelta, timezone exp_payload = { "user_id" : "1234" , "user_name" : "Python" , "exp" : datetime.now(timezone.utc) + timedelta(minutes=1 ) } secret_key = "jc2025" exp_token = jwt.encode(exp_payload, secret_key, algorithm='HS256' )print ("\n带过期时间的JWT Token:" , exp_token)try : decode_secret_key = secret_key decoded_exp_token = jwt.decode(exp_token, decode_secret_key, algorithms=['HS256' ]) print ("解码的Payload:" , decoded_exp_token)except jwt.ExpiredSignatureError: print ("Token已过期!" )except jwt.JWTError: print ("Token验证失败!" )
在上述代码中,我们在 Payload部分加入了超时机制。
如果我们使用正确的secret key且使用时间不超过1分钟,则能正确解析
如果使用错误的secret key,则会报错:Token验证失败!
如果使用了正确的secret key,但使用时间超时,则会报错:Token已过期!
上述三种情况的输出分别如下:
1 2 3 4 5 6 7 8 带过期时间的JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNCIsInVzZXJfbmFtZSI6IlB5dGhvbiIsImV4cCI6MTc1MTI5MTc2MX0 .jMrINdHTy56q19N--P8rLtgBpb2wmFamYaeErEXiMEA 解码的Payload: {'user_id' : '1234' , 'user_name' : 'Python' , 'exp' : 1751291761 } 带过期时间的JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNCIsInVzZXJfbmFtZSI6IlB5dGhvbiIsImV4cCI6MTc1MTI5MTc4Mn0 .W2ecyHE7YaNH58P87dZ5aXqRq9Fy1BAITtrEDtMgSrA Token验证失败! 带过期时间的JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNCIsInVzZXJfbmFtZSI6IlB5dGhvbiIsImV4cCI6MTc1MTI5MTgxMX0 .Kh1StJcVtq59oAvodh_Ul_OOOZFmXWxfKLzBLfold0I Token已过期!
上述Python代码虽然简单,但对我们理解 JWT机制是非常有帮助的。
在FastAPI中使用JWT
成熟的Python Web框架,比如 Flask, FastAPI都对 JWT提供了很好地支持。本节将会介绍如何在FastAPI中使用JWT。
我们使用 FastAPI 实现一个简单的 JWT 服务,用于用户认证,并对相应的 API进行鉴权。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 from datetime import datetime, timedelta, timezonefrom fastapi import FastAPI, Depends, HTTPException, statusfrom fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestFormfrom pydantic import BaseModelfrom jose import JWTError, jwt, ExpiredSignatureError app = FastAPI() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token" ) SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 10 class User (BaseModel ): username: str email: str | None = None class Token (BaseModel ): access_token: str token_type: str fake_users_db = { "admin" : { "username" : "admin" , "email" : "admin@example.com" , "password" : "admin123" }, "user" : { "username" : "user" , "email" : "user@example.com" , "password" : "user123" } }def authenticate_user (username: str , password: str ): user = fake_users_db.get(username) if not user: return False if user["password" ] != password: return False return userdef create_access_token (data: dict , expires_delta: timedelta | None = None ): to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else : expire = datetime.now(timezone.utc) + timedelta(minutes=15 ) to_encode.update({"exp" : expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwtasync def get_current_user (token: str = Depends(oauth2_scheme ) ): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据" , headers={"WWW-Authenticate" : "Bearer" }, ) expired_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token已过期" , headers={"WWW-Authenticate" : "Bearer" }, ) try : payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub" ) if username is None : raise credentials_exception except ExpiredSignatureError: raise expired_exception except JWTError: raise credentials_exception user = fake_users_db.get(username) if user is None : raise credentials_exception return User(username=user["username" ], email=user["email" ])@app.post("/token" , response_model=Token ) async def login_for_access_token (form_data: OAuth2PasswordRequestForm = Depends( ) ): user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误" , headers={"WWW-Authenticate" : "Bearer" }, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub" : user["username" ]}, expires_delta=access_token_expires ) return {"access_token" : access_token, "token_type" : "bearer" }@app.get("/users/me" , response_model=User ) async def read_users_me (current_user: User = Depends(get_current_user ) ): return current_user@app.get("/protected" ) async def protected_route (current_user: User = Depends(get_current_user ) ): return {"message" : f"你好 {current_user.username} ,这是一个受保护的接口!" }if __name__ == "__main__" : import uvicorn uvicorn.run(app, host="0.0.0.0" , port=8000 )
启动上述服务,/token 接口提供了对应用户的JWT Token, /users/me 接口使用JWT认证,用于获取对应用户信息,/protected 接口用于演示受保护的资源。
上面使用JWT机制的Python代码应该不难理解。
我们使用两种工具来测试接口服务,一种是Postman工具,一种是FastAPI自带的Swagger页面。
以Postman工具为例,/token 接口测试如下:
在 Authorization 页面填入上述JWT Token,请求 /users/me 接口,返回结果如下:
如果不输入Token,则请求结果如下:
使用FastAPI自带的Swagger页面,测试接口将会非常方便。
授权页面如下:
请求接口如下:
JWT实战
JWT 尝尝用于用户认证,因此,一般需要用户认证的接口都可以用 JWT机制来授权与认证。
最近,AI Coding非常火爆,笔者在日常工作中使用的工具为Cursor。笔者使用Cursor写了一个JWT实战项目,基于FastAPI和MySQL的现代化学生成绩管理系统,支持JWT身份认证,具有老师和学生两种角色权限管理。该项目已上传至Github,网址为:https://github.com/percent4/student_grade_management 。
这里不再过多介绍这个项目的实现代码和具体的用户身份认证,只讲下这个实战项目的主要功能,有兴趣的读者可以自行阅读该项目代码。该项目的主要功能如下:
🔐 安全认证: JWT令牌认证,bcrypt密码加密
👥 角色管理: 老师和学生角色,权限分离
📊 成绩管理: 支持语文、数学、英语三科成绩录入与查看
🔧 密码管理: 用户可自主修改初始密码
📱 响应式界面: 现代化UI设计,支持移动端访问
🚀 实时更新: 成绩修改实时生效,数据同步更新
🛡️ 数据安全: 完整的输入验证和权限控制
主要功能的页面截图如下:
总结
本文主要介绍了什么是HTTP请求中的鉴权,JWT的基本原理和结构,并简单演示了如何使用Python来生成和验证JWT。
在Python的Web框架中,实现JWT用户身份认证是非常方便的,本文也给出了这方面的例子。
最后,笔者使用Cursor,实现了一个学生成绩管理系统,其中的用户身份认证用到了JWT机制。
如果你能完整地读完上面的文章,并对上述Python代码认真阅读与测试,相信你一定能掌握JWT方面的基础知识了。
本文到此结束,后续笔者将会持续更新HTTP鉴权方面的文章~
参考文献
前后端常见的几种鉴权方式: https://juejin.cn/post/6844903927100473357
JWT 认证及其在 FastAPI 中的使用: https://krau.top/posts/fastapi-jwt
JSON Web Token 入门教程: https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JWT(JSON Web Token)原理、使用方法及使用注意事项: https://zhuanlan.zhihu.com/p/662299933
JWT 验证工具的网站: https://jwt.io/