本文介绍通过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",
},
},
}
有关身份验证方法、当前注意事项以及带有DefaultAzureCredential和ManagedIdentityCredential的TOKEN示例的完整列表,请参阅在 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];
对于不在生产环境中运行迁移的应用程序,请省略 ALTER 和 CREATE 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 |
INSERT、UPDATE、DELETE在所有用户表上 |
运行时应用程序用户(与 db_datareader 组合使用) |
| db_ddladmin | 创建、更改和删除架构对象 | 仅限迁移或部署用户 |
| db_owner | 所有数据库权限,包括安全性 | 应用程序应避免使用;保留给 DBA 使用 |
若要实现比预定义角色所允许的更精细的控制,请创建自定义数据库角色,并 GRANT 仅授予应用所使用的特定架构上的特定权限。 将所有应用程序对象保留在专用架构中(例如, app)允许你使用 GRANT ... ON SCHEMA::app 范围授予,而不是依赖于数据库范围的 db_datareader 和 db_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_AGE 并 CONN_HEALTH_CHECKS 防止连接过时。 |
低 |