在现代Web开发中,API安全是每个开发者必须面对的挑战。传统基于Session的认证机制在分布式系统中存在扩展性问题,而JWT(JSON Web Token)作为一种轻量级的认证方案,已经成为保护API的主流选择。我曾在多个电商和金融项目中实施JWT方案,其无状态特性显著降低了服务器负载,同时保持了良好的安全水平。
JWT本质上是一个经过数字签名的JSON对象,由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。当用户登录成功后,服务器生成JWT返回给客户端,客户端在后续请求中携带这个Token,服务器只需验证签名有效性即可确认用户身份。这种机制特别适合前后端分离架构和微服务场景。
注意:JWT虽然方便,但需要特别注意Token过期时间和刷新机制的设计,避免安全风险。
一个典型的JWT看起来像这样:
code复制eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
json复制{
"alg": "HS256",
"typ": "JWT"
}
json复制{
"sub": "1234567890", // 用户ID
"name": "John Doe", // 自定义声明
"iat": 1516239022 // 签发时间
}
常用的签名算法有:
在金融级应用中,我推荐使用RS256算法。曾有一个项目最初使用HS256,后来因为密钥泄露导致安全事件,迁移到RS256后安全性得到显著提升。
bash复制pip install pyjwt cryptography
对于需要RS256算法的场景,还需生成RSA密钥对:
bash复制openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
python复制import jwt
import datetime
from fastapi import HTTPException, status
# 配置项
SECRET_KEY = "your-secret-key" # HS256使用
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 生成Token
def create_access_token(data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.datetime.utcnow() + expires_delta
else:
expire = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# 验证Token
def verify_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token已过期",
headers={"WWW-Authenticate": "Bearer"},
)
except jwt.JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效Token",
headers={"WWW-Authenticate": "Bearer"},
)
python复制from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login_for_access_token():
# 实际项目应验证用户名密码
access_token = create_access_token(
data={"sub": "user123"},
expires_delta=datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
payload = verify_token(token)
return {"message": "访问成功", "user_id": payload.get("sub")}
python复制REFRESH_TOKEN_EXPIRE_DAYS = 7
def create_refresh_token(data: dict):
to_encode = data.copy()
expire = datetime.datetime.utcnow() + datetime.timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
to_encode.update({"exp": expire, "type": "refresh"})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@app.post("/refresh")
async def refresh_token(refresh_token: str):
payload = verify_token(refresh_token)
if payload.get("type") != "refresh":
raise HTTPException(status_code=400, detail="无效的刷新令牌")
new_token = create_access_token({"sub": payload.get("sub")})
return {"access_token": new_token}
python复制from enum import Enum
class Role(str, Enum):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
def check_permission(token: str, required_role: Role):
payload = verify_token(token)
user_role = payload.get("role", Role.GUEST)
if user_role != required_role:
raise HTTPException(status_code=403, detail="权限不足")
return payload
@app.get("/admin")
async def admin_route(token: str = Depends(oauth2_scheme)):
payload = check_permission(token, Role.ADMIN)
return {"message": "管理员访问成功"}
python-jose| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 立即失效 | 签名密钥变更 | 确保所有服务使用相同密钥 |
| 随机失效 | 时钟不同步 | 同步服务器时间,使用NTP服务 |
| 部分失效 | 算法不匹配 | 检查生成和验证使用相同算法 |
python复制from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
在实际项目中,我曾遇到移动端缓存导致Token失效的问题。最终解决方案是在Token生成时加入版本号,客户端检测到版本变更时强制刷新登录。这个经验告诉我,设计认证系统时不仅要考虑安全性,还要考虑各种边缘场景。