Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    affaan-m

    django-security

    affaan-m/django-security
    Security
    42,727
    4 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Django安全最佳实践,身份验证,授权,CSRF保护,SQL注入预防,XSS预防和安全部署配置。

    SKILL.md

    Django 安全最佳实践

    保护 Django 应用程序免受常见漏洞侵害的全面安全指南。

    何时启用

    • 设置 Django 认证和授权时
    • 实现用户权限和角色时
    • 配置生产环境安全设置时
    • 审查 Django 应用程序的安全问题时
    • 将 Django 应用程序部署到生产环境时

    核心安全设置

    生产环境设置配置

    # settings/production.py
    import os
    
    DEBUG = False  # CRITICAL: Never use True in production
    
    ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
    
    # Security headers
    SECURE_SSL_REDIRECT = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_HSTS_SECONDS = 31536000  # 1 year
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    SECURE_BROWSER_XSS_FILTER = True
    X_FRAME_OPTIONS = 'DENY'
    
    # HTTPS and Cookies
    SESSION_COOKIE_HTTPONLY = True
    CSRF_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    CSRF_COOKIE_SAMESITE = 'Lax'
    
    # Secret key (must be set via environment variable)
    SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
    if not SECRET_KEY:
        raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')
    
    # Password validation
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
            'OPTIONS': {
                'min_length': 12,
            }
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    

    认证

    自定义用户模型

    # apps/users/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    class User(AbstractUser):
        """Custom user model for better security."""
    
        email = models.EmailField(unique=True)
        phone = models.CharField(max_length=20, blank=True)
    
        USERNAME_FIELD = 'email'  # Use email as username
        REQUIRED_FIELDS = ['username']
    
        class Meta:
            db_table = 'users'
            verbose_name = 'User'
            verbose_name_plural = 'Users'
    
        def __str__(self):
            return self.email
    
    # settings/base.py
    AUTH_USER_MODEL = 'users.User'
    

    密码哈希

    # Django uses PBKDF2 by default. For stronger security:
    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

    会话管理

    # Session configuration
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # Or 'db'
    SESSION_CACHE_ALIAS = 'default'
    SESSION_COOKIE_AGE = 3600 * 24 * 7  # 1 week
    SESSION_SAVE_EVERY_REQUEST = False
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # Better UX, but less secure
    

    授权

    权限

    # models.py
    from django.db import models
    from django.contrib.auth.models import Permission
    
    class Post(models.Model):
        title = models.CharField(max_length=200)
        content = models.TextField()
        author = models.ForeignKey(User, on_delete=models.CASCADE)
    
        class Meta:
            permissions = [
                ('can_publish', 'Can publish posts'),
                ('can_edit_others', 'Can edit posts of others'),
            ]
    
        def user_can_edit(self, user):
            """Check if user can edit this post."""
            return self.author == user or user.has_perm('app.can_edit_others')
    
    # views.py
    from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
    from django.views.generic import UpdateView
    
    class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
        model = Post
        permission_required = 'app.can_edit_others'
        raise_exception = True  # Return 403 instead of redirect
    
        def get_queryset(self):
            """Only allow users to edit their own posts."""
            return Post.objects.filter(author=self.request.user)
    

    自定义权限

    # permissions.py
    from rest_framework import permissions
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """Allow only owners to edit objects."""
    
        def has_object_permission(self, request, view, obj):
            # Read permissions allowed for any request
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # Write permissions only for owner
            return obj.author == request.user
    
    class IsAdminOrReadOnly(permissions.BasePermission):
        """Allow admins to do anything, others read-only."""
    
        def has_permission(self, request, view):
            if request.method in permissions.SAFE_METHODS:
                return True
            return request.user and request.user.is_staff
    
    class IsVerifiedUser(permissions.BasePermission):
        """Allow only verified users."""
    
        def has_permission(self, request, view):
            return request.user and request.user.is_authenticated and request.user.is_verified
    

    基于角色的访问控制 (RBAC)

    # models.py
    from django.contrib.auth.models import AbstractUser, Group
    
    class User(AbstractUser):
        ROLE_CHOICES = [
            ('admin', 'Administrator'),
            ('moderator', 'Moderator'),
            ('user', 'Regular User'),
        ]
        role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
    
        def is_admin(self):
            return self.role == 'admin' or self.is_superuser
    
        def is_moderator(self):
            return self.role in ['admin', 'moderator']
    
    # Mixins
    class AdminRequiredMixin:
        """Mixin to require admin role."""
    
        def dispatch(self, request, *args, **kwargs):
            if not request.user.is_authenticated or not request.user.is_admin():
                from django.core.exceptions import PermissionDenied
                raise PermissionDenied
            return super().dispatch(request, *args, **kwargs)
    

    SQL 注入防护

    Django ORM 保护

    # GOOD: Django ORM automatically escapes parameters
    def get_user(username):
        return User.objects.get(username=username)  # Safe
    
    # GOOD: Using parameters with raw()
    def search_users(query):
        return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
    
    # BAD: Never directly interpolate user input
    def get_user_bad(username):
        return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # VULNERABLE!
    
    # GOOD: Using filter with proper escaping
    def get_users_by_email(email):
        return User.objects.filter(email__iexact=email)  # Safe
    
    # GOOD: Using Q objects for complex queries
    from django.db.models import Q
    def search_users_complex(query):
        return User.objects.filter(
            Q(username__icontains=query) |
            Q(email__icontains=query)
        )  # Safe
    

    使用 raw() 的额外安全措施

    # If you must use raw SQL, always use parameters
    User.objects.raw(
        'SELECT * FROM users WHERE email = %s AND status = %s',
        [user_input_email, status]
    )
    

    XSS 防护

    模板转义

    {# Django auto-escapes variables by default - SAFE #}
    {{ user_input }}  {# Escaped HTML #}
    
    {# Explicitly mark safe only for trusted content #}
    {{ trusted_html|safe }}  {# Not escaped #}
    
    {# Use template filters for safe HTML #}
    {{ user_input|escape }}  {# Same as default #}
    {{ user_input|striptags }}  {# Remove all HTML tags #}
    
    {# JavaScript escaping #}
    <script>
        var username = {{ username|escapejs }};
    </script>
    

    安全字符串处理

    from django.utils.safestring import mark_safe
    from django.utils.html import escape
    
    # BAD: Never mark user input as safe without escaping
    def render_bad(user_input):
        return mark_safe(user_input)  # VULNERABLE!
    
    # GOOD: Escape first, then mark safe
    def render_good(user_input):
        return mark_safe(escape(user_input))
    
    # GOOD: Use format_html for HTML with variables
    from django.utils.html import format_html
    
    def greet_user(username):
        return format_html('<span class="user">{}</span>', escape(username))
    

    HTTP 头部

    # settings.py
    SECURE_CONTENT_TYPE_NOSNIFF = True  # Prevent MIME sniffing
    SECURE_BROWSER_XSS_FILTER = True  # Enable XSS filter
    X_FRAME_OPTIONS = 'DENY'  # Prevent clickjacking
    
    # Custom middleware
    from django.conf import settings
    
    class SecurityHeaderMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            response = self.get_response(request)
            response['X-Content-Type-Options'] = 'nosniff'
            response['X-Frame-Options'] = 'DENY'
            response['X-XSS-Protection'] = '1; mode=block'
            response['Content-Security-Policy'] = "default-src 'self'"
            return response
    

    CSRF 防护

    默认 CSRF 防护

    # settings.py - CSRF is enabled by default
    CSRF_COOKIE_SECURE = True  # Only send over HTTPS
    CSRF_COOKIE_HTTPONLY = True  # Prevent JavaScript access
    CSRF_COOKIE_SAMESITE = 'Lax'  # Prevent CSRF in some cases
    CSRF_TRUSTED_ORIGINS = ['https://example.com']  # Trusted domains
    
    # Template usage
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Submit</button>
    </form>
    
    # AJAX requests
    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;
    }
    
    fetch('/api/endpoint/', {
        method: 'POST',
        headers: {
            'X-CSRFToken': getCookie('csrftoken'),
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data)
    });
    

    豁免视图(谨慎使用)

    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt  # Only use when absolutely necessary!
    def webhook_view(request):
        # Webhook from external service
        pass
    

    文件上传安全

    文件验证

    import os
    from django.core.exceptions import ValidationError
    
    def validate_file_extension(value):
        """Validate file extension."""
        ext = os.path.splitext(value.name)[1]
        valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
        if not ext.lower() in valid_extensions:
            raise ValidationError('Unsupported file extension.')
    
    def validate_file_size(value):
        """Validate file size (max 5MB)."""
        filesize = value.size
        if filesize > 5 * 1024 * 1024:
            raise ValidationError('File too large. Max size is 5MB.')
    
    # models.py
    class Document(models.Model):
        file = models.FileField(
            upload_to='documents/',
            validators=[validate_file_extension, validate_file_size]
        )
    

    安全的文件存储

    # settings.py
    MEDIA_ROOT = '/var/www/media/'
    MEDIA_URL = '/media/'
    
    # Use a separate domain for media in production
    MEDIA_DOMAIN = 'https://media.example.com'
    
    # Don't serve user uploads directly
    # Use whitenoise or a CDN for static files
    # Use a separate server or S3 for media files
    

    API 安全

    速率限制

    # settings.py
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ],
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/day',
            'user': '1000/day',
            'upload': '10/hour',
        }
    }
    
    # Custom throttle
    from rest_framework.throttling import UserRateThrottle
    
    class BurstRateThrottle(UserRateThrottle):
        scope = 'burst'
        rate = '60/min'
    
    class SustainedRateThrottle(UserRateThrottle):
        scope = 'sustained'
        rate = '1000/day'
    

    API 认证

    # settings.py
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.TokenAuthentication',
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework_simplejwt.authentication.JWTAuthentication',
        ],
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
    }
    
    # views.py
    from rest_framework.decorators import api_view, permission_classes
    from rest_framework.permissions import IsAuthenticated
    
    @api_view(['GET', 'POST'])
    @permission_classes([IsAuthenticated])
    def protected_view(request):
        return Response({'message': 'You are authenticated'})
    

    安全头部

    内容安全策略

    # settings.py
    CSP_DEFAULT_SRC = "'self'"
    CSP_SCRIPT_SRC = "'self' https://cdn.example.com"
    CSP_STYLE_SRC = "'self' 'unsafe-inline'"
    CSP_IMG_SRC = "'self' data: https:"
    CSP_CONNECT_SRC = "'self' https://api.example.com"
    
    # Middleware
    class CSPMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            response = self.get_response(request)
            response['Content-Security-Policy'] = (
                f"default-src {CSP_DEFAULT_SRC}; "
                f"script-src {CSP_SCRIPT_SRC}; "
                f"style-src {CSP_STYLE_SRC}; "
                f"img-src {CSP_IMG_SRC}; "
                f"connect-src {CSP_CONNECT_SRC}"
            )
            return response
    

    环境变量

    管理密钥

    # Use python-decouple or django-environ
    import environ
    
    env = environ.Env(
        # set casting, default value
        DEBUG=(bool, False)
    )
    
    # reading .env file
    environ.Env.read_env()
    
    SECRET_KEY = env('DJANGO_SECRET_KEY')
    DATABASE_URL = env('DATABASE_URL')
    ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
    
    # .env file (never commit this)
    DEBUG=False
    SECRET_KEY=your-secret-key-here
    DATABASE_URL=postgresql://user:password@localhost:5432/dbname
    ALLOWED_HOSTS=example.com,www.example.com
    

    记录安全事件

    # settings.py
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'file': {
                'level': 'WARNING',
                'class': 'logging.FileHandler',
                'filename': '/var/log/django/security.log',
            },
            'console': {
                'level': 'INFO',
                'class': 'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.security': {
                'handlers': ['file', 'console'],
                'level': 'WARNING',
                'propagate': True,
            },
            'django.request': {
                'handlers': ['file'],
                'level': 'ERROR',
                'propagate': False,
            },
        },
    }
    

    快速安全检查清单

    检查项 描述
    DEBUG = False 切勿在生产环境中启用 DEBUG
    仅限 HTTPS 强制 SSL,使用安全 Cookie
    强密钥 对 SECRET_KEY 使用环境变量
    密码验证 启用所有密码验证器
    CSRF 防护 默认启用,不要禁用
    XSS 防护 Django 自动转义,不要在用户输入上使用 &#124;safe
    SQL 注入 使用 ORM,切勿在查询中拼接字符串
    文件上传 验证文件类型和大小
    速率限制 限制 API 端点访问频率
    安全头部 CSP、X-Frame-Options、HSTS
    日志记录 记录安全事件
    更新 保持 Django 及其依赖项为最新版本

    请记住:安全是一个过程,而非产品。请定期审查并更新您的安全实践。

    Recommended Servers
    ThinAir Data
    ThinAir Data
    OpenZeppelin
    OpenZeppelin
    Cloudflare
    Cloudflare
    Repository
    affaan-m/everything-claude-code
    Files