mssql-django 的安全最佳做法

本文介绍通过mssql-django后端连接到SQL Server的 Django 应用程序的安全做法。 这些做法补充了 Django 的内置安全功能和SQL Server的安全模型。

使用Microsoft Entra身份验证而不是密码

Microsoft Entra身份验证消除了存储的数据库密码。 将其用于所有Azure SQL连接。

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "Authentication=ActiveDirectoryMsi",
        },
    },
}

有关身份验证方法、当前注意事项以及带有DefaultAzureCredentialManagedIdentityCredentialTOKEN示例的完整列表,请参阅在 mssql-django 中使用 Microsoft Entra 进行身份验证

安全地管理凭据

需要 SQL 身份验证时,请将凭据保留在源代码外。

环境变量

import os

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ.get("DB_PORT", "1433"),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Azure 密钥保管库

对于生产部署,请从Azure 密钥保管库检索机密:

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://<your-vault>.vault.azure.net/", credential=credential)

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": client.get_secret("db-name").value,
        "USER": client.get_secret("db-user").value,
        "PASSWORD": client.get_secret("db-password").value,
        "HOST": client.get_secret("db-host").value,
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

注意

切勿将凭据提交到源代码版本控制系统。 将 .env文件添加到 .gitignore。 使用 git-secrets 或预提交钩子扫描是否意外提交了凭据。

强制实施 TLS 加密

SQL Server连接应始终加密。 从 ODBC 驱动程序 18 开始,ODBC 驱动程序默认加密连接:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>.database.windows.net",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            # Encryption is on by default with Driver 18
        },
    },
}

如果使用 ODBC 驱动程序 17,请显式启用加密:

"extra_params": "Encrypt=yes"

注意

TrustServerCertificate=yes仅用于具有自签名证书的本地开发。 请勿在生产环境中使用它。 它禁用证书链验证并增加中间攻击者的风险。 在服务器上安装受信任的证书,并使用 TrustServerCertificate=no 连接。

应用最低权限原则

仅使用应用程序所需的权限创建专用SQL Server登录名:

-- Create a login and user for the application
CREATE LOGIN [django_app]
WITH PASSWORD = '<strong-password>';

USE [<your-database>];

CREATE USER [django_app] FOR LOGIN [django_app];

-- Grant minimum required permissions
-- Read and write data
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

-- Allow Django to create and alter tables during migrations
GRANT ALTER ON SCHEMA::dbo TO [django_app];
GRANT CREATE TABLE TO [django_app];
GRANT REFERENCES ON SCHEMA::dbo TO [django_app];

对于不在生产环境中运行迁移的应用程序,请省略 ALTERCREATE TABLE 权限:

-- Production application user (read/write only)
ALTER ROLE db_datareader ADD MEMBER [django_app];
ALTER ROLE db_datawriter ADD MEMBER [django_app];

GRANT EXECUTE ON SCHEMA::dbo TO [django_app]; -- If using stored procedures

在单独的、权限更高的部署步骤中运行迁移:

-- Migration user (used only during deployments)
ALTER ROLE db_ddladmin ADD MEMBER [django_migrations];

选择正确的角色

SQL Server 固定数据库角色按权限从低到高排序。 选择涵盖工作负荷的最小特权角色,并仅在需要时升级:

角色 Grants 何时使用
db_datareader SELECT 所有用户表和视图 只读报表用户
db_datawriter INSERTUPDATEDELETE在所有用户表上 运行时应用程序用户(与 db_datareader 组合使用)
db_ddladmin 创建、更改和删除架构对象 仅限迁移或部署用户
db_owner 所有数据库权限,包括安全性 应用程序应避免使用;保留给 DBA 使用

若要实现比预定义角色所允许的更精细的控制,请创建自定义数据库角色,并 GRANT 仅授予应用所使用的特定架构上的特定权限。 将所有应用程序对象保留在专用架构中(例如, app)允许你使用 GRANT ... ON SCHEMA::app 范围授予,而不是依赖于数据库范围的 db_datareaderdb_datawriter 角色。

Note

不要将 sa 帐户或 db_owner 固定数据库角色用于应用程序连接。 如果应用程序遭到入侵,攻击者将获得完整的数据库控制。

防止 SQL 注入

Django 的 ORM 会自动对所有查询进行参数化处理。 仅当使用原始 SQL 时,SQL 注入才存在风险:

安全的 ORM 查询

# Django parameterizes these automatically
users = User.objects.filter(email=user_input)
products = Product.objects.filter(price__lte=max_price)

安全:参数化的原始 SQL

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
        "SELECT * FROM products WHERE category = %s AND price < %s",
        [category, max_price],
    )

不安全:原始 SQL 中的字符串格式

# NEVER do this - vulnerable to SQL injection
cursor.execute(f"SELECT * FROM products WHERE category = '{category}'")
cursor.execute("SELECT * FROM products WHERE category = '%s'" % category)

Extra 和 RawSQL

Django 的 extra()RawSQL() 接受原始 SQL 片段。 始终使用参数:

# Safe - parameterized
Product.objects.extra(where=["category = %s"], params=[category])

from django.db.models.expressions import RawSQL
Product.objects.annotate(
    discount=RawSQL("price * %s", [discount_rate])
)

Important

切勿使用 extra()RawSQL() 字符串格式设置。 这些参数绕过 ORM 的自动参数化。

配置 Django 安全中间件

启用 Django 的内置安全中间件来保护 Web 层。 虽然这些不是特定于数据库的,但它们保护连接到数据库的应用程序:

# settings.py

# HTTPS enforcement
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Cookie security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

# Content security
SECURE_CONTENT_TYPE_NOSNIFF = True

审计数据库访问

启用SQL Server审核以跟踪 Django 应用程序中的数据库操作:

-- Create a server audit (Azure SQL uses Azure SQL Auditing instead)
CREATE SERVER AUDIT [DjangoAudit]
TO FILE (FILEPATH = 'C:\Audits\')
WITH (ON_FAILURE = CONTINUE);

ALTER SERVER AUDIT [DjangoAudit] WITH (STATE = ON);

-- Create a database audit specification
USE [<your-database>];

CREATE DATABASE AUDIT SPECIFICATION [DjangoDbAudit]
FOR SERVER AUDIT [DjangoAudit]
ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY [django_app])
WITH (STATE = ON);

对于Azure SQL 数据库,请通过Azure门户或Azure CLI启用审核:

az sql db audit-policy update --resource-group <rg> --server <server> \
    --name <database> --state Enabled \
    --storage-account <storage-account>

使用 Always Encrypted 保护敏感列

对于敏感数据(如 SSN、信用卡号或工资数据)的列级加密,请使用 Always Encrypted。 ODBC 驱动程序以透明方式处理加密和解密:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "ColumnEncryption=Enabled",
        },
    },
}

有关详细设置,包括使用 Azure 密钥保管库 进行密钥管理,请参阅 mssql-django 的 Always Encrypted

安全清单

类别 练习 Priority
Authentication 对Azure SQL使用Microsoft Entra身份验证。
Credentials 将机密存储在环境变量或Azure 密钥保管库中。
Encryption 使用 ODBC 驱动程序 18(默认启用加密)或 Encrypt=yes
注入 使用 ORM 查询或参数化的原始 SQL。 从不采用字符串格式 SQL。
最小特权 创建具有最低所需权限的专用登录名。
TLS 请勿在生产环境中使用 TrustServerCertificate=yes
Django 启用 SECURE_SSL_REDIRECT、保护 Cookie、HSTS。 中等
Auditing 启用SQL Server审核或Azure SQL审核。 中等
列加密 对高度敏感的列使用 Always Encrypted。
连接 设置 CONN_MAX_AGECONN_HEALTH_CHECKS 防止连接过时。