You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django应用中存储用户专属第三方API密钥的最佳实践与可选方案

Django用户第三方API密钥检索的最佳实践
  • 加密存储,杜绝明文
    绝对不能将API密钥以明文形式存在数据库中。推荐使用django-fernet-fields提供的EncryptedCharField,它基于Fernet加密算法,自动处理加密和解密,无需手动实现复杂逻辑。也可以用cryptography库自行封装加密方法:

    from cryptography.fernet import Fernet
    from django.conf import settings
    
    def encrypt_key(raw_key):
        fernet = Fernet(settings.FERNET_KEY)
        return fernet.encrypt(raw_key.encode()).decode()
    
    def decrypt_key(encrypted_key):
        fernet = Fernet(settings.FERNET_KEY)
        return fernet.decrypt(encrypted_key.encode()).decode()
    

    注意:FERNET_KEY要存在环境变量中,不要硬编码到代码里。

  • 独立模型,关联用户
    建议创建单独的凭证模型,而非直接在User模型加字段,方便后续扩展多第三方服务的场景。示例:

    from django.db import models
    from django.contrib.auth.models import User
    from fernet_fields import EncryptedCharField
    
    class ThirdPartyAPICredential(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        api_key = EncryptedCharField(max_length=255)
        service_name = models.CharField(max_length=100)  # 区分不同第三方服务
        updated_at = models.DateTimeField(auto_now=True)
    
        class Meta:
            verbose_name = "第三方API凭证"
    

    若用户需绑定多个服务密钥,可改用ForeignKey并添加unique_together = ('user', 'service_name')约束。

  • 基于当前用户检索,严格权限控制
    从请求上下文获取当前用户,仅查询该用户关联的密钥,杜绝跨用户访问:

    def get_current_user_api_key(request, service_name="default"):
        if not request.user.is_authenticated:
            return None  # 或抛出未认证异常
        try:
            credential = ThirdPartyAPICredential.objects.get(
                user=request.user,
                service_name=service_name
            )
            return credential.api_key  # Fernet字段自动解密
        except ThirdPartyAPICredential.DoesNotExist:
            return None  # 处理无密钥场景,比如引导用户配置
    

    在DRF视图中可配合自定义权限类,强化权限校验:

    from rest_framework.permissions import BasePermission
    
    class IsCredentialOwner(BasePermission):
        def has_object_permission(self, request, view, obj):
            return obj.user == request.user
    
  • 缓存优化,减少数据库查询
    若密钥检索频繁,可将解密后的密钥缓存到Redis或Django内置缓存中,设置合理过期时间:

    from django.core.cache import cache
    
    def get_current_user_api_key(request, service_name="default"):
        cache_key = f"user_{request.user.id}_api_key_{service_name}"
        cached_key = cache.get(cache_key)
        if cached_key:
            return cached_key
        
        # 数据库查询逻辑...
        api_key = credential.api_key
        cache.set(cache_key, api_key, timeout=3600)  # 缓存1小时
        return api_key
    

    密钥更新时要同步清理对应缓存,避免返回旧值。

  • 防止泄露,规避敏感输出
    禁止将API密钥写入日志、返回给前端(除非业务必须,且需通过HTTPS传输)。前端若临时使用密钥,用完立即销毁,不要存储在localStorage或cookie中。

  • 支持密钥轮换,提供更新入口
    开发密钥更新功能,确保用户可随时替换密钥,更新时重新加密存储并清理缓存:

    def update_api_key(request):
        if request.method != "POST":
            return HttpResponseBadRequest("无效请求")
        new_key = request.POST.get("new_api_key")
        service_name = request.POST.get("service_name", "default")
        if not new_key:
            return HttpResponseBadRequest("请提供新密钥")
        
        credential, _ = ThirdPartyAPICredential.objects.get_or_create(
            user=request.user,
            service_name=service_name
        )
        credential.api_key = encrypt_key(new_key)
        credential.save()
        cache.delete(f"user_{request.user.id}_api_key_{service_name}")
        return HttpResponse("密钥更新成功")
    

内容的提问来源于stack exchange,提问作者md12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:05:13