SMTP 服务器搭建完全指南 / 第 8 章:SPF 发件人策略框架
第 8 章:SPF 发件人策略框架
SPF 告诉全世界:“只有这些服务器有权代表我的域名发送邮件。”
8.1 SPF 概述
8.1.1 什么是 SPF
SPF(Sender Policy Framework)是一种邮件认证机制,允许域名所有者通过 DNS TXT 记录声明哪些 IP 地址/服务器有权代表该域名发送邮件。
工作原理:
1. 发送方在 DNS 中发布 SPF 记录
example.com TXT "v=spf1 mx a ip4:203.0.113.10 -all"
2. 接收方收到邮件后,查询发件域名的 SPF 记录
3. 接收方检查发送服务器 IP 是否在 SPF 记录中
4. 根据检查结果返回:
• PASS — IP 被授权
• FAIL — IP 未被授权
• SOFTFAIL — IP 可能未被授权(建议)
• NEUTRAL — 不确定
• NONE — 无 SPF 记录
8.1.2 SPF 的作用
| 作用 | 说明 |
|---|---|
| 防止域名欺骗 | 阻止冒用你的域名发送邮件 |
| 提高送达率 | 通过 SPF 验证的邮件更少被拒收 |
| 减少垃圾邮件 | 帮助接收方识别伪造邮件 |
| 配合 DMARC | SPF 是 DMARC 策略的基础之一 |
8.1.3 SPF 的局限性
| 局限 | 说明 |
|---|---|
| 仅验证发件 IP | 不能验证邮件内容 |
| 转发问题 | 邮件转发后 SPF 验证可能失败 |
| 需要配合 DKIM | SPF 单独使用效果有限 |
| 查询限制 | DNS 查询不能超过 10 次 |
8.2 SPF 记录语法
8.2.1 SPF 记录结构
v=spf1 <机制1> <机制2> ... <限定词>
基本格式:
v=spf1 [指令] [指令] ... [默认策略]
8.2.2 限定词(Qualifier)
| 限定词 | 含义 | 返回结果 | 说明 |
|---|---|---|---|
+ | Pass | PASS | 允许(默认) |
- | Fail | FAIL | 拒绝 |
~ | SoftFail | SOFTFAIL | 软拒绝(建议标记为垃圾) |
? | Neutral | NEUTRAL | 中立(不做判断) |
8.2.3 机制(Mechanism)
| 机制 | 说明 | 示例 |
|---|---|---|
all | 匹配所有(通常放在最后) | -all |
ip4 | 匹配 IPv4 地址/范围 | ip4:203.0.113.10 |
ip6 | 匹配 IPv6 地址/范围 | ip6:2001:db8::/32 |
a | 匹配域名的 A 记录 | a |
mx | 匹配域名的 MX 记录 | mx |
include | 引用其他域名的 SPF 记录 | include:_spf.google.com |
exists | 检查域名是否存在 A 记录 | exists:%{i}._spf.example.com |
redirect | 重定向到其他域名的 SPF | redirect=_spf.example.com |
8.2.4 修饰符(Modifier)
| 修饰符 | 说明 | 示例 |
|---|---|---|
include | 引用外部 SPF | include:_spf.google.com |
redirect | 重定向 | redirect=_spf.example.com |
exp | 自定义解释 | exp=explain._spf.example.com |
8.3 配置 SPF 记录
8.3.1 基本 SPF 记录
; 允许域名的 MX 记录中的服务器发送
example.com. IN TXT "v=spf1 mx -all"
; 允许域名的 A 记录中的服务器发送
example.com. IN TXT "v=spf1 a -all"
; 允许 MX 和 A 记录中的服务器发送
example.com. IN TXT "v=spf1 mx a -all"
8.3.2 常见配置模板
场景 1:单服务器
; 只允许一台服务器发送
example.com. IN TXT "v=spf1 ip4:203.0.113.10 -all"
场景 2:多服务器
; 允许多台服务器发送
example.com. IN TXT "v=spf1 ip4:203.0.113.10 ip4:203.0.113.11 ip4:203.0.113.12 -all"
场景 3:包含第三方服务
; 允许自己的服务器和 Google Workspace
example.com. IN TXT "v=spf1 mx a include:_spf.google.com -all"
场景 4:复杂配置
; 允许 MX、特定 IP、第三方服务
example.com. IN TXT "v=spf1 mx ip4:203.0.113.0/24 include:_spf.google.com include:spf.protection.outlook.com -all"
8.3.3 第三方服务 SPF 记录
| 服务 | SPF include |
|---|---|
| Google Workspace | include:_spf.google.com |
| Microsoft 365 | include:spf.protection.outlook.com |
| Amazon SES | include:amazonses.com |
| SendGrid | include:sendgrid.net |
| Mailchimp | include:servers.mcsv.net |
| Zoho Mail | include:zoho.com |
| 阿里企业邮 | include:spf.mxhichina.com |
| 腾讯企业邮 | include:spf.mail.qq.com |
8.3.4 SPF 记录查询限制
SPF 记录中的 include、mx、a 等机制会产生 DNS 查询。RFC 7208 规定:
| 限制 | 说明 |
|---|---|
| 最大查询次数 | 10 次 |
| 最大 void 查询 | 2 次 |
| TXT 记录长度 | 建议不超过 255 字符 |
查询次数计算:
每个机制的 DNS 查询次数:
- ip4/ip6: 0 次(无 DNS 查询)
- a: 1 次
- mx: 2 次(MX + A)
- include: 1+ 次(递归计算)
- exists: 1 次
8.4 使用 spf-tools 工具
8.4.1 安装工具
# 安装 Python SPF 测试工具
pip install pyspf pydns
# 或使用在线工具
# https://www.kitterman.com/spf/validate.html
8.4.2 命令行验证 SPF
# 使用 dig 查询 SPF 记录
dig TXT example.com +short
# 使用 nslookup
nslookup -type=TXT example.com
# 使用 Python 验证 SPF
python3 -c "
import spf
result, explanation = spf.check2(i='203.0.113.10', s='[email protected]', h='mail.example.com')
print(f'SPF 结果: {result}')
print(f'解释: {explanation}')
"
8.4.3 SPF 验证脚本
#!/bin/bash
# spf-check.sh — SPF 记录检查脚本
DOMAIN="$1"
if [ -z "$DOMAIN" ]; then
echo "用法: $0 <域名>"
exit 1
fi
echo "=== SPF 记录检查: $DOMAIN ==="
# 1. 查询 SPF 记录
echo "[1/4] 查询 SPF TXT 记录..."
SPF_RECORD=$(dig +short TXT $DOMAIN | grep "v=spf1")
if [ -z "$SPF_RECORD" ]; then
echo " ❌ 未找到 SPF 记录"
exit 1
else
echo " ✅ SPF 记录: $SPF_RECORD"
fi
# 2. 检查 SPF 版本
echo "[2/4] 检查 SPF 版本..."
if echo "$SPF_RECORD" | grep -q "^v=spf1"; then
echo " ✅ SPF 版本正确: v=spf1"
else
echo " ❌ SPF 版本格式错误"
fi
# 3. 检查默认策略
echo "[3/4] 检查默认策略..."
if echo "$SPF_RECORD" | grep -q "\-all"; then
echo " ✅ 默认策略: -all (Fail,推荐)"
elif echo "$SPF_RECORD" | grep -q "~all"; then
echo " ⚠️ 默认策略: ~all (SoftFail,建议使用 -all)"
elif echo "$SPF_RECORD" | grep -q "?all"; then
echo " ⚠️ 默认策略: ?all (Neutral,不推荐)"
elif echo "$SPF_RECORD" | grep -q "+all"; then
echo " ❌ 默认策略: +all (Pass,危险!)"
else
echo " ⚠️ 未找到默认策略"
fi
# 4. 检查 DNS 查询次数
echo "[4/4] 估算 DNS 查询次数..."
QUERY_COUNT=0
for mech in include mx a; do
COUNT=$(echo "$SPF_RECORD" | grep -o "$mech" | wc -l)
if [ "$mech" = "mx" ]; then
QUERY_COUNT=$((QUERY_COUNT + COUNT * 2))
else
QUERY_COUNT=$((QUERY_COUNT + COUNT))
fi
done
echo " 估算查询次数: $QUERY_COUNT / 10"
if [ $QUERY_COUNT -gt 10 ]; then
echo " ❌ 超过 SPF 查询限制(10 次)"
else
echo " ✅ 查询次数在限制内"
fi
echo ""
echo "=== 检查完成 ==="
8.5 SPF 与邮件转发
8.8.1 转发问题
SPF 转发问题示意:
发送方 (IP: 1.1.1.1)
│
▼
转发服务器 (IP: 2.2.2.2) ← SPF 检查失败!
│ 因为 2.2.2.2 不在发送方的 SPF 记录中
▼
接收方
解决方案:
1. 使用 SRS (Sender Rewriting Scheme)
2. 依赖 DKIM 签名(转发后仍有效)
3. 使用 DMARC 策略(综合 SPF 和 DKIM)
8.5.2 SRS 配置
# 安装 postsrsd
sudo apt install -y postsrsd
# 配置 Postfix
sudo postconf -e "sender_canonical_maps = tcp:127.0.0.1:10001"
sudo postconf -e "sender_canonical_classes = envelope_sender"
sudo postconf -e "recipient_canonical_maps = tcp:127.0.0.1:10002"
sudo postconf -e "recipient_canonical_classes = envelope_recipient"
# 启动 postsrsd
sudo systemctl enable --now postsrsd
# 重新加载 Postfix
sudo systemctl reload postfix
8.6 SPF 验证结果处理
8.6.1 Postfix 中的 SPF 检查
# 安装 SPF 检查工具
sudo apt install -y postfix-policyd-spf-python
# /etc/postfix/main.cf
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
check_policy_service unix:private/policyd-spf,
reject_rbl_client zen.spamhaus.org
# /etc/postfix/master.cf — 添加 SPF 策略服务
policyd-spf unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf
8.6.2 SPF 结果与 DMARC
SPF 验证结果对 DMARC 的影响:
SPF PASS + 对齐检查通过 → DMARC PASS
SPF PASS + 对齐检查失败 → DMARC 可能失败(取决于 DKIM)
SPF FAIL + 对齐检查失败 → DMARC FAIL(除非 DKIM PASS)
SPF SOFTFAIL → DMARC FAIL(除非 DKIM PASS)
注意:SPF 验证和 DMARC 对齐是两回事!
- SPF 验证:检查发送 IP 是否被授权
- DMARC 对齐:检查 MAIL FROM 域名与 From 头域名是否匹配
8.7 业务场景:多服务商 SPF 配置
场景描述
一家企业使用以下服务发送邮件:
- 自建邮件服务器 (203.0.113.10)
- Google Workspace
- Microsoft 365
- SendGrid 营销邮件
- 客服系统 Zendesk
SPF 配置
; 企业 SPF 记录
example.com. IN TXT "v=spf1 \
ip4:203.0.113.10/32 \
include:_spf.google.com \
include:spf.protection.outlook.com \
include:sendgrid.net \
include:mail.zendesk.com \
-all"
DNS 查询次数计算:
| 机制 | 查询次数 |
|---|---|
| ip4:203.0.113.10/32 | 0 |
| include:_spf.google.com | 1 (+递归) |
| include:spf.protection.outlook.com | 1 (+递归) |
| include:sendgrid.net | 1 |
| include:mail.zendesk.com | 1 |
| 总计 | 约 8 次 ✅ |
8.8 常见问题排查
问题 1:SPF 验证失败
# 检查 SPF 记录
dig TXT example.com +short
# 检查发送服务器 IP
curl -s ifconfig.me
# 验证 IP 是否在 SPF 记录中
# 使用在线工具: https://www.kitterman.com/spf/validate.html
问题 2:DNS 查询超限
症状:邮件日志中出现 "too many DNS lookups"
解决方案:
1. 减少 include 数量
2. 使用 ip4/ip6 代替 include(不产生 DNS 查询)
3. 使用 SPF 扁平化服务
- https://dmarcian.com/spf-survey/
- https://www.spftools.com/
问题 3:邮件转发后 SPF 失败
症状:转发的邮件被标记为垃圾邮件
解决方案:
1. 配置 SRS(Sender Rewriting Scheme)
2. 确保 DKIM 签名有效(DKIM 不受转发影响)
3. 使用 DMARC 策略(SPF 或 DKIM 任一通过即可)
8.9 注意事项
⚠️ SPF 记录数量限制:
- 每个域名只能有一条 SPF TXT 记录
- 多条 SPF 记录会导致验证失败
- 如需合并多个 SPF 记录,使用
include或ip4/ip6
⚠️ SPF 查询限制:
- 总 DNS 查询次数不超过 10 次
- 超过限制会导致验证失败(PermError)
- 使用在线工具检查查询次数
💡 SPF 最佳实践:
- 使用
-all而不是~all- 定期审查 SPF 记录,移除不再使用的服务
- 优先使用
ip4/ip6(不产生 DNS 查询)- 配合 DKIM 和 DMARC 使用
8.10 扩展阅读
上一章:← 第 7 章:DKIM 邮件签名 下一章:第 9 章:DMARC 策略与报告 →