如何确保Django+DRF开发的API仅被自有CSR前端访问?
针对CSR前端的DRF API保护方案
1. 基于请求头的签名验证
在前端请求时动态生成签名并添加到请求头,后端通过约定规则验证签名合法性,大幅提高非法调用的门槛:
- 实现逻辑:
- 前端:每次请求时,用与后端协商的密钥(建议构建时从环境变量注入,避免硬编码),对请求URL、时间戳、JWT令牌的payload部分做HMAC-SHA256哈希,生成签名后放入自定义请求头
X-Request-Signature,同时带上时间戳X-Request-Timestamp。 - 后端(DRF自定义权限类):
- 校验时间戳是否在有效范围(比如5分钟),防止重放攻击。
- 用相同密钥和算法重新计算签名,与前端传来的签名比对。
- 签名一致则允许访问,否则拒绝。
- 前端:每次请求时,用与后端协商的密钥(建议构建时从环境变量注入,避免硬编码),对请求URL、时间戳、JWT令牌的payload部分做HMAC-SHA256哈希,生成签名后放入自定义请求头
- DRF权限类示例:
from rest_framework.permissions import BasePermission import hmac import hashlib from datetime import datetime, timedelta from django.conf import settings class SignedRequestPermission(BasePermission): def has_permission(self, request, view): secret_key = settings.FRONTEND_SIGN_SECRET.encode('utf-8') timestamp = request.META.get('HTTP_X_REQUEST_TIMESTAMP') signature = request.META.get('HTTP_X_REQUEST_SIGNATURE') jwt_token = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[-1] if not timestamp or not signature or not jwt_token: return False # 校验时间戳有效期 try: request_time = datetime.fromtimestamp(int(timestamp)) if datetime.now() - request_time > timedelta(minutes=5): return False except ValueError: return False # 生成待签名字符串 payload_part = jwt_token.split('.')[1] sign_string = f"{request.path}#{timestamp}#{payload_part}".encode('utf-8') computed_signature = hmac.new(secret_key, sign_string, hashlib.sha256).hexdigest() return hmac.compare_digest(computed_signature, signature)
2. 结合JWT的前端指纹验证
在JWT的payload中加入前端唯一标识,后端每次请求校验该标识的一致性:
- 实现步骤:
- 用户登录时,前端生成唯一指纹(比如
hash(user-agent + 本地存储的UUID)),随登录请求传给后端。 - 后端生成JWT时,将指纹加入payload(如
frontend_fingerprint字段)。 - 后续API请求,前端在请求头
X-Frontend-Fingerprint中带上该指纹,后端校验JWT中的指纹与请求中的是否匹配。
- 用户登录时,前端生成唯一指纹(比如
- DRF权限类示例:
from rest_framework.permissions import BasePermission import jwt from django.conf import settings class FingerprintPermission(BasePermission): def has_permission(self, request, view): token = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[-1] fingerprint = request.META.get('HTTP_X_FRONTEND_FINGERPRINT') if not token or not fingerprint: return False try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) return payload.get('frontend_fingerprint') == fingerprint except jwt.InvalidTokenError: return False
3. 动态API路径/参数混淆
定期或根据请求上下文动态调整API的访问规则:
- 比如前端根据当前时间戳的哈希值生成API路径后缀(如
/api/v1/data/{hash(timestamp)}),后端提前计算合法后缀,仅匹配的路径允许访问。 - 或在请求中添加动态生成的
nonce参数,后端校验参数是否符合约定的生成规则。
4. 强化JWT安全策略
基于已有的JWT机制进一步优化:
- 缩短JWT过期时间(比如15分钟),配合刷新令牌机制,减少令牌泄露后的风险。
- 强制使用HTTPS传输,避免令牌被中间人劫持。
- 启用JWT黑名单机制,用户注销或令牌泄露时可及时失效。
关键提醒
CSR前端的代码本身是暴露的,不存在绝对完美的防护方案。上述方案的核心是提高非法调用的技术门槛,再结合你已遵循的最小权限原则(API仅返回必要数据),能有效降低恶意调用的风险。
内容的提问来源于stack exchange,提问作者mostafa ghobashy
相关产品推荐
相关产品推荐

