JWT 认证与鉴权实战:结构、签名与安全设计
【摘要】 一、为什么用 JWT传统 Session 方案把登录状态存在服务端,靠 Cookie 里的 sessionId 去查。服务一多、要水平扩展时,就得引入共享存储(Redis)来同步会话,或者做会话粘滞,比较麻烦。JWT(JSON Web Token)把“用户是谁、有什么权限”编码进一个自带签名的令牌里,服务端无需存会话,只要用密钥验签就能确认令牌没被篡改。这让它在无状态 API、微服务、前后...
一、为什么用 JWT
传统 Session 方案把登录状态存在服务端,靠 Cookie 里的 sessionId 去查。服务一多、要水平扩展时,就得引入共享存储(Redis)来同步会话,或者做会话粘滞,比较麻烦。
JWT(JSON Web Token)把“用户是谁、有什么权限”编码进一个自带签名的令牌里,服务端无需存会话,只要用密钥验签就能确认令牌没被篡改。这让它在无状态 API、微服务、前后端分离场景里非常流行。
二、JWT 的结构
一个 JWT 由三段用 . 连接的 Base64Url 字符串组成:
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ . 签名
Header Payload Signature
- Header:声明签名算法(如
HS256、RS256)。 - Payload:承载 claims,如
sub(用户)、exp(过期时间)、role(角色)、自定义字段。 - Signature:对
header.payload用密钥/私钥签名,防止篡改。
关键认知:Payload 只是 Base64 编码,不是加密,任何人都能解开看内容。所以绝不能在 JWT 里放密码、身份证等敏感信息。
三、用 Python 生成与校验
import jwt, time
SECRET = "your-256-bit-secret"
def issue_token(user_id, role):
payload = {
"sub": user_id,
"role": role,
"iat": int(time.time()),
"exp": int(time.time()) + 3600, # 1 小时后过期
}
return jwt.encode(payload, SECRET, algorithm="HS256")
def verify_token(token):
try:
return jwt.decode(token, SECRET, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
raise ValueError("token 已过期")
except jwt.InvalidTokenError:
raise ValueError("token 无效")
校验时必须显式指定算法白名单(algorithms=["HS256"]),否则存在 “alg=none / 算法混淆” 攻击风险。
四、HS256 还是 RS256
- HS256(对称):签发和校验用同一密钥,简单高效,适合单体或可信内部服务。
- RS256(非对称):私钥签名、公钥验签。签发方只在一处持有私钥,各资源服务只拿公钥验签,适合微服务/多方场景,私钥泄露面更小。
原则:签名密钥绝不能下发到客户端;微服务架构优先 RS256,让验证方只持有公钥。
五、刷新令牌与安全设计
JWT 一旦签发,在过期前服务端难以主动作废(无状态的好处也是麻烦)。常见做法:
- 短寿命 Access Token(如 15 分钟)+ 长寿命 Refresh Token(如 7 天)。
- Access 过期后用 Refresh 换新,Refresh 存服务端可撤销(如 Redis 白名单)。
- 登出/封禁:把要作废的 token(或 jti)加入黑名单,校验时查一下黑名单。
- Refresh Token 轮换:每次刷新换发新的 refresh,旧的作废,降低被盗用风险。
六、必须防的几个坑
- 存哪:放
localStorage易被 XSS 读取;更稳妥是 HttpOnly + Secure Cookie(防 JS 读取),并配 SameSite 防 CSRF。 - 不要塞敏感数据:Payload 明文可读,别放密码/密钥。
- 一定要设
exp并校验;不要接受永不过期的 token。 - 算法白名单:拒绝
alg=none,固定期望算法。 - 传输必须 HTTPS:否则 token 在链路上被截获。
- 签名密钥要够强:HS256 密钥太短可被暴力破解。
七、一个校验中间件示例
from functools import wraps
from flask import request, jsonify
def auth_required(roles=None):
def deco(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify(msg="缺少令牌"), 401
try:
claims = verify_token(auth[7:])
except ValueError as e:
return jsonify(msg=str(e)), 401
if roles and claims.get("role") not in roles:
return jsonify(msg="无权限"), 403
request.user = claims
return fn(*args, **kwargs)
return wrapper
return deco
八、JWT vs Session 怎么选
- 无状态 API、微服务、跨域前后端分离、移动端:JWT 合适。
- 需要强会话控制、即时踢下线、单体内应用:传统 Session + 服务端存储更简单可控。
- 二者也可结合:JWT 做短期身份凭证,服务端保留少量可撤销状态。
九、总结
JWT 的核心是自包含 + 签名防篡改 + 无状态校验。用好它的关键是:Payload 不放敏感信息、算法固定白名单、必备 exp、短寿命 Access + 可撤销 Refresh、令牌存 HttpOnly Cookie、全程 HTTPS。记住这些,就能在享受无状态便利的同时避开 JWT 常见的安全坑。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)