如何配置Django API仅接收自身站点请求并拦截外部非法调用
首先明确一个前提:所有运行在用户浏览器侧的代码、请求头、参数都是完全公开可篡改的,不存在任何纯前端配置能彻底阻止他人复制请求调用你的接口,你需要通过多层后端校验把非法调用的成本拉到足够高,同时避免核心密钥泄露。
多层防护实现方案
1. 基础防护:开启CSRF校验
CSRF是Django自带的基础防护能力,可以挡住跨站伪造的请求,注意不要只靠Origin、Referer头做校验——这两个头客户端可以随意伪造,只能拦住最低级的爬虫。
- 配置Django settings:
- 确认
django.middleware.csrf.CsrfViewMiddleware在中间件列表中开启 - Heroku部署时必须添加配置
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'),否则反向代理会导致HTTPS请求识别异常 - 配置
CSRF_TRUSTED_ORIGINS,只填入你自己的站点域名,不要留通配符 - 开启
SESSION_COOKIE_SECURE = True、CSRF_COOKIE_SECURE = True,强制Cookie只在HTTPS连接下传输 ALLOWED_HOSTS严格配置为自己的域名,禁止使用*通配
- 确认
- 修改前端请求逻辑,带上CSRF令牌,同站请求优先用相对路径避免跨域问题:
// 从Cookie读取CSRF令牌的工具函数 function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } const csrftoken = getCookie('csrftoken'); var clientSecret = await fetch('/api/', { method: 'POST', body: params, headers: { 'Content-Type': 'text/plain', 'X-CSRFToken': csrftoken }, credentials: 'same-origin' // 同站请求自动携带Cookie }).then(r => r.json())
注意:CSRF校验只能挡跨站发起的请求,挡不住用户手动在控制台抓包、复制参数从本地发请求,必须叠加下一层防护。
2. 核心防护:短期一次性签名校验(防重放)
这部分是防止他人复制请求重放的核心:每次发敏感请求前,先向后端申请一个有效期极短、和当前用户会话绑定、只能使用一次的随机签名,签名验证不通过直接拒绝请求。
- 后端实现示例(缓存可以用Heroku的Redis插件,不要把签名存在前端或Cookie里):
from rest_framework.request import Request as RESTRequest from rest_framework.response import Response from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework import status from django.core.cache import cache import uuid import requests # 发放临时签名的接口,页面加载或发起敏感请求前先调用 @api_view(['GET']) def get_request_sign(request): if not request.session.session_key: request.session.create() # 生成随机签名,缓存有效期设为10-15秒即可 sign = uuid.uuid4().hex cache.set(f"pay_sign_{request.session.session_key}", sign, timeout=15) return Response({"sign": sign}) @api_view(['POST']) # 如果接口需要登录才能访问,加上下面的装饰器 # @permission_classes([IsAuthenticated]) def payment(request, *args, **kwargs): # 校验会话合法性 if not request.session.session_key: return Response({"error": "非法请求"}, status=status.HTTP_403_FORBIDDEN) # 校验签名 req_sign = request.headers.get('X-Request-Sign', '') cache_key = f"pay_sign_{request.session.session_key}" valid_sign = cache.get(cache_key) if not valid_sign or req_sign != valid_sign: return Response({"error": "非法请求"}, status=status.HTTP_403_FORBIDDEN) # 校验通过立刻删除缓存中的签名,防止重放 cache.delete(cache_key) # 原有业务逻辑 ... #define headers_in and params_in here # 注意:出站接口的密钥只能存在Heroku环境变量里,绝对不能传到前端 response = requests.post('https://outboundapirequest.com/v1/request', headers=headers_in, data=params_in) return Response(response.json()['value'])
- 前端对应修改,发敏感请求前先获取临时签名:
// 先拿临时签名 const signRes = await fetch('/api/get-sign/', { credentials: 'same-origin' }).then(r=>r.json()); // 再发敏感请求 var clientSecret = await fetch('/api/', { method: 'POST', body: params, headers: { 'Content-Type': 'text/plain', 'X-CSRFToken': csrftoken, 'X-Request-Sign': signRes.sign }, credentials: 'same-origin' }).then(r => r.json())
3. 补充防护:频率与参数校验
- 给敏感接口加DRF频率限制,比如同一个IP/会话1分钟内最多允许调用3次,防止接口被批量刷取
- 所有业务参数必须做后端校验,比如支付类接口绝对不能信任前端传的金额、商品ID参数,必须以后端存储的订单数据为准
- 开启
SECURE_SSL_REDIRECT = True,强制所有请求跳转到HTTPS,防止传输链路被窃听 - 所有敏感请求打日志,记录请求IP、用户ID、参数、时间,出现异常调用可以快速溯源
额外注意事项
- 不要在静态JS文件里写任何固定的秘钥字符串,这类静态文件任何人都可以直接查看,等于没有防护
- 不要试图通过判断浏览器指纹、前端JS环境混淆等方式做拦截,这类手段都可以被绕过,核心校验必须放在后端
- 你现在用后端代理调用第三方敏感接口的逻辑是正确的,第三方接口的密钥只能存在Heroku的环境变量配置中,绝对不能出现在前端代码或接口返回值里
内容的提问来源于stack exchange,提问作者bernardo
相关产品推荐
相关产品推荐

