Django如何识别第一方应用访问?API权限方案咨询
Django API多认证方案优化:自身应用与第三方客户端权限区分
场景梳理
你的Django应用现状:
- 自身前端基于模板,用JS fetch调用API,依赖allauth的Django内置会话认证,不用dj-rest-auth
- Discord机器人作为第三方,用dj-rest-auth的
Authorization: Bearer令牌认证 - 现在要给其他第三方客户端加
Authorization: Api-Key认证,同时得让自身应用正常访问API,还要实现差异化权限和限流
先分析你想到的两个方案,再给你更稳妥的优化思路:
方案1:中间件自动插入API Key
思路
写个中间件,识别自身应用的请求(比如通过IP、请求头),自动把合法的API Key塞进请求头,让自身请求走API Key认证逻辑。
优缺点
- 优点:不用改前端和视图代码,逻辑简单粗暴
- 缺点:
- 中间件层面篡改请求头,后期排查认证问题时容易绕晕
- 自身请求的识别逻辑如果不严谨,很容易被伪造请求绕过
- 自身和第三方共用一套API Key机制,后续想给第三方单独做限流、加特殊权限会非常受限
方案2:自定义IsUsingFirstPartyApp权限类 + 逻辑或校验
思路
自定义权限类,通过HTTP_ORIGIN/HTTP_REFERER判断请求来自自身站点,然后在视图的permission_classes里用IsUsingFirstPartyApp | HasAPIKey(DRF支持逻辑或权限),满足任一条件就能访问。
优缺点
- 优点:权限逻辑清晰,自身和第三方的认证方式明确分开,方便后续拆分权限和限流规则
- 缺点:
ORIGIN/REFERER头是可以伪造的,单纯靠这俩判断有安全风险- 如果有内部后台任务调用API(比如Celery),这类请求没有浏览器头,会被拦截
推荐方案:多维度校验的自定义权限 + 分组配置
针对方案2的安全短板,强化校验维度,同时保留灵活的权限配置:
1. 强化IsFirstPartyApp权限类的安全性
不要只依赖请求头,结合会话Cookie、可信IP、自定义内部头多维度校验,大幅降低伪造风险:
from rest_framework.permissions import BasePermission from django.contrib.sessions.models import Session from django.utils import timezone from ipaddress import ip_address, ip_network from django.conf import settings class IsFirstPartyApp(BasePermission): def has_permission(self, request, view): # 校验1:是否携带有效的Django会话Cookie(自身登录用户的请求会自动带) session_cookie = request.COOKIES.get(settings.SESSION_COOKIE_NAME) if session_cookie: try: session = Session.objects.get(session_key=session_cookie) if session.expire_date > timezone.now(): return True except Session.DoesNotExist: pass # 校验2:请求IP是否在可信内部IP列表(比如服务器本机、内网IP) trusted_ips = getattr(settings, 'TRUSTED_INTERNAL_IPS', ['127.0.0.1']) client_ip = request.META.get('REMOTE_ADDR') try: client_ip_obj = ip_address(client_ip) for ip_range in trusted_ips: if client_ip_obj in ip_network(ip_range, strict=False): return True except ValueError: pass # 校验3:自定义内部请求头(需在Nginx等反向代理层限制仅内部请求能添加) if request.META.get('HTTP_X_INTERNAL_REQUEST') == 'true': return True # 校验4:兜底校验ORIGIN/REFERER(针对前端同域请求) allowed_origins = settings.ALLOWED_HOSTS + ['http://localhost:8000'] origin = request.META.get('HTTP_ORIGIN') referer = request.META.get('HTTP_REFERER') if origin in allowed_origins: return True if referer and any(referer.startswith(o) for o in allowed_origins): return True return False
2. 灵活配置视图权限
根据不同视图的需求组合权限:
- 允许自身应用+第三方访问:
permission_classes = [IsFirstPartyApp | HasAPIKey] - 仅允许自身应用访问:
permission_classes = [IsFirstPartyApp] - 仅允许第三方API Key访问:
permission_classes = [HasAPIKey] - 需要自身登录用户或内部请求,同时还要API Key的特殊场景:
from rest_framework.permissions import IsAuthenticated permission_classes = [(IsAuthenticated | IsFirstPartyApp) & HasAPIKey]
3. 差异化限流配置
利用DRF的限流功能,给不同认证方式的请求设置不同速率:
# settings.py REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.ScopedRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { 'first_party': '1000/hour', # 自身应用限流宽松 'third_party': '100/hour', # 第三方严格限流 } } # 视图中动态选择限流规则(可以封装成自定义限流类更优雅) class ExampleAPIView(APIView): permission_classes = [IsFirstPartyApp | HasAPIKey] def get_throttles(self): if IsFirstPartyApp().has_permission(self.request, self): self.throttle_scope = 'first_party' else: self.throttle_scope = 'third_party' return super().get_throttles()
额外注意点
- 自身应用的会话认证和第三方的API Key认证要明确分工:同域前端用会话,跨域服务用API Key,不要混着来
- 在Nginx等反向代理层做前置校验,比如拦截伪造的
X-Internal-Request头,或者限制第三方API Key请求的IP范围 - 给第三方客户端提供API Key管理界面,支持密钥轮换、权限配置
- 记录所有API请求的认证方式、来源IP、用户信息,方便审计和排查问题
内容的提问来源于stack exchange,提问作者gmcc051
相关产品推荐
相关产品推荐

