09-4. SMTP 認證:從開放中繼到安全驗證
深入理解郵件認證的演進、為什麼早期不需要密碼、以及現代認證機制
目錄
🔐 SMTP 認證:從開放中繼到安全驗證
⏱️ 閱讀時間: 12 分鐘 🎯 難度: ⭐⭐⭐ (中等)
🏗️ SMTP 認證在網路模型中的位置
OSI 7 層模型
┌──────────────────────────────┬─────────────────────────┐
│ 7. Application Layer (應用層) │ SMTP + AUTH │ ← 認證在這裡
├──────────────────────────────┼─────────────────────────┤
│ 6. Presentation Layer (表示層)│ Base64 編碼 │
├──────────────────────────────┼─────────────────────────┤
│ 5. Session Layer (會話層) │ TLS (加密) │
├──────────────────────────────┼─────────────────────────┤
│ 4. Transport Layer (傳輸層) │ TCP │
├──────────────────────────────┼─────────────────────────┤
│ 3. Network Layer (網路層) │ IP │
├──────────────────────────────┼─────────────────────────┤
│ 2. Data Link Layer (資料鏈結層)│ Ethernet │
├──────────────────────────────┼─────────────────────────┤
│ 1. Physical Layer (實體層) │ 網路線、光纖 │
└──────────────────────────────┴─────────────────────────┘重點:
- SMTP 認證在應用層實現
- 使用 Base64 編碼傳遞帳號密碼
- 必須搭配 TLS/SSL 加密(否則密碼會被竊聽)
🤔 核心問題:為什麼有些情況不用密碼也能寄信?
生活化比喻
早期郵局(1980 年代的 SMTP):
┌──────────────────────────────┐
│ 任何人都可以投信到郵筒 │
│ 郵局不檢查身份 │
│ 只要寫了收件地址就寄出 │
└──────────────────────────────┘
就像:
你走到路邊的郵筒
投入一封信
不需要出示身份證
郵局就會幫你寄 ✅
問題:
❌ 有人濫用郵筒寄垃圾信
❌ 有人偽造寄件地址
❌ 郵局被當成免費的垃圾信發送工具
現代郵局(現在的 SMTP):
┌──────────────────────────────┐
│ 需要帳號密碼才能寄信 │
│ 驗證你的身份 │
│ 防止濫用 │
└──────────────────────────────┘
但仍有例外:
✅ 內部員工寄信(公司內網)
✅ 特定白名單 IP
✅ 本機測試環境📜 歷史:SMTP 認證的演進
時間線 ⭐⭐⭐
1982 年 📧 SMTP 誕生 (RFC 821)
└─ 完全沒有認證機制
└─ 設計理念:「互信的學術網路」
└─ 任何人都可以寄信給任何人
問題背景:
- 當時網際網路只有學術機構使用
- 大家互相信任
- 沒有商業利益
- 沒有垃圾郵件的概念
───────────────────────────────────────
1990 年代初 💥 垃圾郵件開始出現
└─ 網際網路商業化
└─ 行銷人員發現可以免費發廣告
└─ 開放中繼(Open Relay)被濫用
濫用案例:
攻擊者找到開放中繼伺服器
↓
利用它發送數百萬封垃圾信
↓
被寄出的郵件顯示來自該伺服器
↓
該伺服器 IP 被列入黑名單
↓
正常用戶也無法寄信了 ❌
───────────────────────────────────────
1995 年 🔒 SMTP AUTH 提出 (RFC 2554)
└─ 增加認證機制
└─ 需要帳號密碼才能寄信
改變:
寄信前必須先 AUTH LOGIN
↓
提供正確的帳號密碼
↓
驗證通過才能發信 ✅
───────────────────────────────────────
1999 年 📮 多數 ISP 要求認證
└─ 預設關閉開放中繼
└─ Port 25 通常被封鎖
└─ 改用 Port 587 (Submission)
───────────────────────────────────────
2010 年代 🔐 現代安全機制
└─ 強制 TLS 加密
└─ OAuth2 認證
└─ 應用程式專用密碼
└─ 雙因素驗證 (2FA)
───────────────────────────────────────
現在(2020+)🛡️ 多層防護
├─ SPF(IP 驗證)
├─ DKIM(簽章驗證)
├─ DMARC(政策執行)
├─ OAuth2(現代認證)
└─ 應用程式密碼🚪 開放中繼 (Open Relay) 的問題
什麼是開放中繼?
開放中繼 = 不需要認證就接受外部郵件轉發
傳統設計:
任何人 → SMTP 伺服器 → 任何人
└─ 不驗證身份
└─ 不檢查來源
└─ 直接轉發
就像:
公共郵局不檢查身份
任何人都可以請郵局幫忙寄信
不管你是不是本地居民被濫用的場景
案例 1:垃圾郵件發送者
垃圾郵件發送者 (Spammer)
│
├─ 掃描網路尋找開放中繼
│ (使用自動化工具)
│
├─ 找到:mail.company.com (開放中繼)
│
├─ 透過它發送 100 萬封垃圾信
│ From: fake@example.com
│ Subject: 快速致富!點這裡!
│
└─ 後果:
├─ mail.company.com 被列入黑名單
├─ 公司正常員工無法寄信
├─ 頻寬被吃光
└─ 法律責任(幫助發送垃圾信)案例 2:釣魚郵件
攻擊者
│
├─ 利用開放中繼
│
├─ 偽造高管郵件
│ From: ceo@company.com
│ Subject: 緊急!請立即匯款
│
└─ 員工收到後信以為真 ❌案例 3:DDoS 放大攻擊
攻擊者
│
├─ 找到 1000 個開放中繼
│
├─ 向每個中繼發送郵件
│ 收件者:victim@target.com
│
└─ victim 收到 1000 倍流量
→ 郵件伺服器癱瘓 ❌🔍 如何檢測開放中繼?
測試方法
使用 Telnet 測試:
# 連線到 SMTP 伺服器
telnet mail.example.com 25
# 如果連線成功,嘗試發送郵件
S: 220 mail.example.com SMTP Ready
C: HELO test.com
S: 250 mail.example.com
# 嘗試寄信給外部地址(不認證)
C: MAIL FROM:<spammer@evil.com>
S: 250 OK
C: RCPT TO:<victim@other-domain.com>
# 如果回應 250 OK = 開放中繼 ❌
S: 250 OK ← 危險!
# 如果回應 550 = 有保護 ✅
S: 550 Relaying denied ← 安全!使用線上工具:
MXToolbox Open Relay Test:
https://mxtoolbox.com/diagnostic.aspx
功能:
✅ 自動測試開放中繼
✅ 檢查黑名單狀態
✅ DNS 記錄驗證🔐 SMTP AUTH:認證機制
認證流程
完整認證會話:
客戶端 伺服器
│ │
├─ EHLO client.example.com ────────>│
│<──────────────────────── 250-SIZE │
│ 250-AUTH │
│ PLAIN │
│ LOGIN │
│ CRAM-MD5 │
│ │
├─ AUTH LOGIN ─────────────────────>│
│<───────────────── 334 VXNlcm5hbWU6│
│ (Base64: Username:)
│ │
├─ YWxpY2U= ───────────────────────>│
│ (Base64: alice) │
│<───────────────── 334 UGFzc3dvcmQ6│
│ (Base64: Password:)
│ │
├─ c2VjcmV0MTIz ───────────────────>│
│ (Base64: secret123) │
│ │
│<─────────── 235 Authentication OK ─┤
│ │
├─ MAIL FROM:<alice@example.com> ──>│
│ │
└─ 開始發送郵件... │🔧 認證方式詳解
1. PLAIN(明文)
原理:
將帳號密碼用 Base64 編碼
一次發送:\0username\0password範例:
# 編碼帳號密碼
echo -ne '\000alice\000secret123' | base64
# 輸出:AGFsaWNlAHNlY3JldDEyMw==
# SMTP 會話
C: AUTH PLAIN AGFsaWNlAHNlY3JldDEyMw==
S: 235 Authentication successfulPython 實作:
import smtplib
import base64
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
# PLAIN 認證(自動處理)
server.login('alice@gmail.com', 'password')
# 或手動實作
auth_string = f'\x00alice@gmail.com\x00password'
encoded = base64.b64encode(auth_string.encode()).decode()
server.docmd('AUTH PLAIN', encoded)安全性:
❌ Base64 不是加密!只是編碼
❌ 明文密碼容易被截取
解決方案:
✅ 必須搭配 TLS/SSL
✅ 使用 Port 587 (STARTTLS) 或 465 (SSL)2. LOGIN(分步明文)
原理:
分兩步發送帳號和密碼
每個都用 Base64 編碼範例:
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
(Base64: Username:)
C: YWxpY2U=
(Base64: alice)
S: 334 UGFzc3dvcmQ6
(Base64: Password:)
C: c2VjcmV0MTIz
(Base64: secret123)
S: 235 Authentication successfulPython 實作:
import smtplib
import base64
server = smtplib.SMTP('smtp.example.com', 587)
server.starttls()
# 手動 LOGIN 認證
server.docmd('AUTH LOGIN')
server.docmd(base64.b64encode(b'alice').decode())
server.docmd(base64.b64encode(b'secret123').decode())安全性:
❌ 同樣是明文(只是分步)
✅ 必須搭配 TLS/SSL3. CRAM-MD5(挑戰-回應)
原理:
伺服器發送隨機挑戰碼
客戶端用密碼和挑戰碼計算 MD5
只發送 MD5 雜湊,不發送密碼流程:
C: AUTH CRAM-MD5
S: 334 PDEyMzQ1Njc4OTAuMTIzNDU2QGV4YW1wbGUuY29tPg==
(Base64 編碼的隨機挑戰碼)
客戶端:
1. 解碼挑戰碼
2. 計算 HMAC-MD5(password, challenge)
3. 回應:username + space + hash
C: YWxpY2UgZjFkMmQ0ZjRlMjc5YWJjZjFhMmMzYjRkNWU2Zjc4OTBh
(Base64: alice f1d2d4f4e279abcf1a2c3b4d5e6f7890a)
S: 235 Authentication successfulPython 實作:
import hmac
import hashlib
import base64
def cram_md5_response(username, password, challenge):
"""計算 CRAM-MD5 回應"""
# 解碼挑戰碼
challenge_bytes = base64.b64decode(challenge)
# 計算 HMAC-MD5
digest = hmac.new(
password.encode(),
challenge_bytes,
hashlib.md5
).hexdigest()
# 組合回應
response = f'{username} {digest}'
return base64.b64encode(response.encode()).decode()
# 使用
challenge = 'PDEyMzQ1Njc4OTAuMTIzNDU2QGV4YW1wbGUuY29tPg=='
response = cram_md5_response('alice', 'secret123', challenge)
print(response)安全性:
✅ 密碼不在網路上傳輸
✅ 可以不用 TLS(但仍建議用)
⚠️ MD5 已被認為不安全
⚠️ 容易遭受重放攻擊4. OAuth2(現代標準)⭐⭐⭐
原理:
使用 Access Token 代替密碼
Token 有時效性
權限可控制流程:
1. 用戶授權
┌────────┐
│ Gmail │
└───┬────┘
│
「允許此應用存取你的郵件?」
│
[允許] [拒絕]
│
▼
產生 Access Token
2. 使用 Token 認證
C: AUTH XOAUTH2 dXNlcj1hbGljZUBnbWFpbC5jb20BYXV0aD1CZWFyZXIgeWEyOS...
S: 235 Authentication successful
Token 內容:
- 有效期限(通常 1 小時)
- 權限範圍(只能寄信、不能讀信等)
- 可以撤銷Python 實作(Gmail):
import smtplib
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from base64 import b64encode
# 1. 取得授權
SCOPES = ['https://mail.google.com/']
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES
)
creds = flow.run_local_server(port=0)
# 2. 產生 OAuth2 字串
def generate_oauth2_string(username, access_token):
auth_string = f'user={username}\x01auth=Bearer {access_token}\x01\x01'
return b64encode(auth_string.encode()).decode()
# 3. 使用 OAuth2 認證
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
auth_string = generate_oauth2_string(
'alice@gmail.com',
creds.token
)
server.docmd('AUTH XOAUTH2', auth_string)
# 4. 發送郵件
server.sendmail('alice@gmail.com', 'bob@example.com', msg)
server.quit()優勢:
✅ 不需要儲存密碼
✅ Token 可撤銷
✅ 權限細粒度控制
✅ 支援多因素驗證
✅ 現代應用的標準做法🆚 認證方式對比
完整對比表
| 認證方式 | 安全性 | 實作難度 | TLS 必要性 | 現代使用 |
|---|---|---|---|---|
| 無認證 | ❌ 極差 | 簡單 | N/A | ❌ 已淘汰 |
| PLAIN | ⚠️ 需 TLS | 簡單 | ✅ 必須 | ✅ 常用 |
| LOGIN | ⚠️ 需 TLS | 簡單 | ✅ 必須 | ✅ 常用 |
| CRAM-MD5 | ⚠️ 中等 | 中等 | ⚠️ 建議 | ⚠️ 較少 |
| OAuth2 | ✅ 最佳 | 困難 | ✅ 必須 | ✅ 推薦 |
🔓 什麼情況下不需要認證?
1. 本機測試環境
場景:開發測試
設定:
smtp_host = 'localhost'
smtp_port = 1025
為什麼不需要認證?
✅ 只在本機運行
✅ 不對外開放
✅ 用於開發測試
Python 範例:
# 使用 Python 內建 SMTP 測試伺服器
python -m smtpd -n -c DebuggingServer localhost:1025
# 發送測試郵件(不需認證)
server = smtplib.SMTP('localhost', 1025)
server.sendmail(from_addr, to_addr, msg)
server.quit()2. 公司內部網路
場景:企業內網
設定:
內網 SMTP 伺服器:mail.internal.company.com
只允許內網 IP:192.168.0.0/16
為什麼不需要認證?
✅ 只有公司員工能存取
✅ 基於 IP 白名單
✅ 防火牆保護
配置範例(Postfix):
# /etc/postfix/main.cf
mynetworks = 192.168.0.0/16, 127.0.0.0/8
smtpd_recipient_restrictions =
permit_mynetworks,
reject_unauth_destination3. 伺服器間通訊(MTA to MTA)
場景:郵件伺服器互相轉發
流程:
Gmail Server → Yahoo Server
(Port 25, 無認證)
為什麼不需要認證?
✅ 這是郵件伺服器的正常職責
✅ 使用 SPF/DKIM/DMARC 驗證
✅ 基於 MX 記錄信任
範例:
Alice@gmail.com 寄信給 Bob@yahoo.com
1. Gmail 查詢 yahoo.com 的 MX 記錄
→ mta5.am0.yahoodns.net
2. Gmail 連到 Yahoo MX (Port 25)
→ 不需要認證
→ 直接 MAIL FROM / RCPT TO
3. Yahoo 檢查:
✅ SPF 記錄(IP 是否授權)
✅ DKIM 簽章(郵件完整性)
✅ DMARC 政策(如何處理)4. 白名單 IP
場景:特定服務的固定 IP
設定:
允許 IP:203.0.113.10(公司官網伺服器)
用途:發送「重設密碼」、「訂單確認」等系統郵件
為什麼不需要認證?
✅ IP 固定且可信
✅ 防火牆限制
✅ 簡化自動化流程
配置範例(Postfix):
# /etc/postfix/main.cf
smtpd_recipient_restrictions =
permit_sasl_authenticated,
permit_mynetworks,
check_client_access hash:/etc/postfix/client_whitelist,
reject_unauth_destination
# /etc/postfix/client_whitelist
203.0.113.10 OK5. 應用程式專用密碼
場景:舊版應用程式
問題:
應用程式不支援 OAuth2
但 Google 要求使用 2FA
解決方案:
生成「應用程式專用密碼」
├─ 16 字元隨機密碼
├─ 只用於該應用
└─ 可隨時撤銷
使用方式:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login('alice@gmail.com', 'abcd efgh ijkl mnop') # 應用程式密碼⚠️ 不使用認證的風險
安全問題
1. 開放中繼 (Open Relay)
└─ 被當成垃圾郵件發送工具
└─ IP 被列入黑名單
└─ 法律責任
2. 郵件偽造
└─ 任何人可偽造 From 地址
└─ 釣魚郵件
└─ 社交工程攻擊
3. 資源濫用
└─ 頻寬被吃光
└─ 伺服器過載
└─ 成本增加
4. 無法追蹤
└─ 不知道誰發的郵件
└─ 無法稽核
└─ 出問題難以追查🛡️ 現代最佳實踐
推薦設定
1. 客戶端提交(Port 587)
# 使用 Port 587 + STARTTLS + 認證
submission inet n - n - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject2. 強制 TLS
# Python 範例
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls() # 強制升級為 TLS
server.login(username, password)3. 使用 OAuth2
# 現代應用推薦
from google.oauth2.credentials import Credentials
creds = get_credentials() # OAuth2 流程
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.auth('XOAUTH2', lambda: generate_oauth2_string(creds))4. 應用程式專用密碼
Gmail 設定:
1. 啟用 2FA
2. 生成應用程式密碼
3. 在應用程式中使用該密碼(非真實密碼)5. IP 限制
# Postfix
smtpd_recipient_restrictions =
permit_mynetworks, # 只允許內網
permit_sasl_authenticated, # 或已認證
reject_unauth_destination # 其他拒絕🎓 常見面試題
Q1:為什麼早期的 SMTP 不需要認證?
答案:
歷史背景:
1982 年 SMTP 設計時的環境:
1. 使用者少
└─ 只有大學、研究機構
└─ 幾千個用戶
└─ 大家互相認識
2. 互信環境
└─ 學術網路
└─ 沒有商業利益
└─ 不會有人惡意濫用
3. 技術限制
└─ 頻寬昂貴
└─ 認證增加複雜度
└─ 簡單就是美
4. 沒有垃圾郵件
└─ 1978 年第一封垃圾郵件
└─ 但不常見
└─ 沒有商業化垃圾郵件產業設計理念:
類比:小村莊的信箱
早期網路 = 小村莊
├─ 大家互相認識
├─ 不會有人亂寄信
└─ 郵筒不上鎖 ✅
現代網路 = 大城市
├─ 陌生人很多
├─ 有人專門寄垃圾信
└─ 郵筒必須上鎖 🔒演進過程:
1982-1990: 無認證
└─ 完全開放
└─ 任何人都能寄信
1990-1995: 垃圾郵件爆發
└─ 開放中繼被濫用
└─ 大量垃圾郵件
1995: SMTP AUTH 提出
└─ RFC 2554
└─ 加入認證機制
1999: 廣泛部署
└─ 多數 ISP 要求認證
└─ Port 25 被封鎖
2010+: 現代安全
└─ OAuth2
└─ 應用程式密碼
└─ 多因素驗證記憶技巧:
早期 SMTP = 小村莊郵局
└─ 不鎖門
└─ 互相信任
現代 SMTP = 大城市郵局
└─ 需要身份證
└─ 嚴格管控Q2:PLAIN 和 LOGIN 有什麼不同?
答案:
核心差異:
PLAIN: 一次發送
C: AUTH PLAIN AGFsaWNlAHNlY3JldDEyMw==
└─ \0username\0password (Base64)
LOGIN: 分兩次發送
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: YWxpY2U=
└─ username (Base64)
S: 334 UGFzc3dvcmQ6
C: c2VjcmV0MTIz
└─ password (Base64)詳細對比:
| 特性 | PLAIN | LOGIN |
|---|---|---|
| 發送次數 | 1 次 | 2 次(互動式) |
| 格式 | \0user\0pass | 分別發送 |
| 編碼 | Base64 | Base64 |
| 安全性 | 相同(都是明文) | 相同(都是明文) |
| 複雜度 | 簡單 | 稍複雜 |
| 支援度 | RFC 4616 標準 | 非標準(但常用) |
| TLS 需求 | ✅ 必須 | ✅ 必須 |
實際會話對比:
PLAIN 方式:
─────────────────────────────
C: EHLO client.example.com
S: 250-smtp.example.com
S: 250 AUTH PLAIN LOGIN
C: AUTH PLAIN AGFsaWNlAHNlY3JldDEyMw==
S: 235 Authentication successful
─────────────────────────────
總共:2 個往返
LOGIN 方式:
─────────────────────────────
C: EHLO client.example.com
S: 250-smtp.example.com
S: 250 AUTH PLAIN LOGIN
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: YWxpY2U=
S: 334 UGFzc3dvcmQ6
C: c2VjcmV0MTIz
S: 235 Authentication successful
─────────────────────────────
總共:4 個往返為什麼兩種都存在?
歷史原因:
- LOGIN 出現較早(Microsoft 實作)
- PLAIN 後來標準化(RFC)
- 為了相容性,兩種都保留
實務選擇:
✅ PLAIN:效率較高(1 次往返)
✅ LOGIN:舊系統相容性好
共同要求:
🔒 都必須在 TLS 加密下使用
└─ 否則密碼會被竊聽!Python 實作對比:
import smtplib
import base64
server = smtplib.SMTP('smtp.example.com', 587)
server.starttls()
# 方法 1: 使用 login()(自動選擇 PLAIN 或 LOGIN)
server.login('alice', 'secret123')
# 方法 2: 手動 PLAIN
auth_string = '\x00alice\x00secret123'
server.docmd('AUTH PLAIN', base64.b64encode(auth_string.encode()).decode())
# 方法 3: 手動 LOGIN
server.docmd('AUTH LOGIN')
server.docmd(base64.b64encode(b'alice').decode())
server.docmd(base64.b64encode(b'secret123').decode())Q3:什麼時候應該使用 OAuth2 而不是密碼?
答案:
應該使用 OAuth2 的情況:
1. 第三方應用程式 ⭐⭐⭐
場景:你開發的 Email 客戶端
使用密碼:
User → 輸入 Gmail 密碼 → 你的 App → Gmail
↑
你拿到密碼 ❌
問題:
❌ 你的 App 儲存了用戶的真實密碼
❌ 如果 App 被駭,密碼外洩
❌ 用戶必須完全信任你
❌ 用戶無法限制權限
使用 OAuth2:
User → 授權 → 你的 App 拿到 Token → Gmail
↑
只有 Token,沒有密碼 ✅
優勢:
✅ 你的 App 不知道密碼
✅ Token 可以隨時撤銷
✅ 權限可控(只能寄信、不能刪信等)
✅ 支援 2FA2. 企業應用 / SaaS 服務
場景:
- Slack 發送通知郵件
- Trello 寄送任務提醒
- GitHub 發送合併請求通知
為什麼用 OAuth2:
✅ 不需要儲存每個用戶的密碼
✅ 用戶可以隨時取消授權
✅ 符合安全合規要求
✅ 減少責任風險
範例(Slack):
Slack 請求權限:
「允許 Slack 以你的名義發送郵件?」
[允許] [拒絕]
↓
產生 Token
↓
Slack 使用 Token 發信3. 多因素驗證 (2FA) 環境
問題場景:
User 啟用了 Gmail 2FA
↓
傳統密碼認證失敗
↓
應用程式無法登入 ❌
解決方案 A:應用程式專用密碼
- 生成 16 字元密碼
- 只用於該應用
- 繞過 2FA
⚠️ 仍是密碼,有風險
解決方案 B:OAuth2 ✅
- 在瀏覽器完成 2FA
- 產生 Token
- 應用程式使用 Token
- 安全且支援 2FA4. 行動應用程式
問題:
行動 App 儲存密碼很危險
├─ 手機可能遺失
├─ App 可能被反編譯
└─ 密碼可能被提取
OAuth2 方案:
├─ Token 儲存在 Keychain/Keystore
├─ Token 有時效性(1 小時)
├─ 過期可用 Refresh Token 更新
└─ 遺失手機 → 撤銷 Token ✅5. 微服務架構
場景:
Service A 需要代表用戶發送郵件
傳統方式:
User 密碼 → Service A → Service B → Gmail
↑
儲存密碼 ❌
OAuth2 方式:
User 授權 → Token → Service A → Gmail
↑
只有 Token ✅
優勢:
✅ 權限最小化
✅ 可審計
✅ 易於撤銷不需要 OAuth2 的情況:
1. 個人腳本 / 自動化
└─ 只有你自己用
└─ 應用程式密碼即可
2. 內部系統
└─ 企業內網
└─ 不對外開放
└─ PLAIN/LOGIN + TLS 即可
3. 簡單測試
└─ 開發環境
└─ 測試用途
└─ OAuth2 設定太複雜實作複雜度對比:
密碼認證:
server.login(user, password) # 1 行
OAuth2:
1. 註冊應用程式
2. 設定 Redirect URI
3. 請求授權
4. 交換 Token
5. Refresh Token
6. 使用 Token 認證
# 需要 50+ 行程式碼
結論:
如果是第三方應用 → OAuth2 ✅
如果是自己的工具 → 密碼 OKQ4:如何檢查 SMTP 伺服器支援哪些認證方式?
答案:
方法 1:使用 Telnet
# 連線到 SMTP 伺服器
telnet smtp.gmail.com 587
# 輸出
Trying 142.250.185.109...
Connected to smtp.gmail.com.
Escape character is '^]'.
220 smtp.gmail.com ESMTP
# 發送 EHLO
EHLO client.example.com
# 伺服器回應(查看 AUTH 行)
250-smtp.gmail.com at your service
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER
250-ENHANCEDSTATUSCODES
250 SMTPUTF8
# 解讀
AUTH LOGIN PLAIN XOAUTH2
│ │ └─ OAuth2 (推薦)
│ └─ PLAIN 認證
└─ LOGIN 認證方法 2:使用 OpenSSL(加密連線)
# Port 465 (SSL)
openssl s_client -connect smtp.gmail.com:465 -crlf
# Port 587 (STARTTLS)
openssl s_client -connect smtp.gmail.com:587 -starttls smtp -crlf
# 然後發送 EHLO
EHLO test.com
# 查看 AUTH 行
250-AUTH LOGIN PLAIN XOAUTH2方法 3:使用 Python
import smtplib
def check_auth_methods(host, port=587):
"""檢查 SMTP 伺服器支援的認證方式"""
try:
server = smtplib.SMTP(host, port)
server.set_debuglevel(1) # 顯示詳細資訊
# 發送 EHLO
server.ehlo()
# 如果是 Port 587,啟用 TLS
if port == 587:
server.starttls()
server.ehlo() # TLS 後重新 EHLO
# 檢查支援的認證方式
if server.has_extn('AUTH'):
auth_methods = server.esmtp_features['auth'].split()
print(f'✅ 支援的認證方式: {", ".join(auth_methods)}')
return auth_methods
else:
print('❌ 不支援認證')
return []
except Exception as e:
print(f'❌ 錯誤: {e}')
return []
finally:
try:
server.quit()
except:
pass
# 測試
print('=== Gmail ===')
check_auth_methods('smtp.gmail.com', 587)
print('\n=== Outlook ===')
check_auth_methods('smtp-mail.outlook.com', 587)
print('\n=== Yahoo ===')
check_auth_methods('smtp.mail.yahoo.com', 587)方法 4:使用命令列工具
# 使用 curl (7.66.0+)
curl smtp://smtp.gmail.com:587 -v --ssl-reqd
# 使用 swaks (Swiss Army Knife SMTP)
swaks --server smtp.gmail.com:587 --tls --quit-after EHLO
# 使用 msmtp
msmtp --host=smtp.gmail.com --port=587 --serverinfo常見伺服器的認證方式:
Gmail (smtp.gmail.com:587)
├─ LOGIN
├─ PLAIN
├─ XOAUTH2 (推薦)
└─ OAUTHBEARER
Outlook (smtp-mail.outlook.com:587)
├─ LOGIN
├─ PLAIN
└─ XOAUTH2
Yahoo (smtp.mail.yahoo.com:587)
├─ LOGIN
└─ PLAIN
自架 Postfix (常見設定)
├─ PLAIN
├─ LOGIN
└─ CRAM-MD5完整檢測腳本:
import smtplib
import socket
def comprehensive_check(host, ports=[25, 465, 587]):
"""完整檢查 SMTP 伺服器"""
print(f'檢查主機: {host}\n')
for port in ports:
print(f'─── Port {port} ───')
try:
# 檢查連線
sock = socket.create_connection((host, port), timeout=5)
sock.close()
print('✅ 可連線')
# 檢查認證
if port == 465:
# SSL
server = smtplib.SMTP_SSL(host, port, timeout=10)
else:
# 普通或 STARTTLS
server = smtplib.SMTP(host, port, timeout=10)
server.ehlo()
if port == 587:
# 嘗試 STARTTLS
if server.has_extn('STARTTLS'):
print('✅ 支援 STARTTLS')
server.starttls()
server.ehlo()
else:
print('❌ 不支援 STARTTLS')
# 檢查認證方式
if server.has_extn('AUTH'):
methods = server.esmtp_features['auth'].split()
print(f'✅ 認證方式: {", ".join(methods)}')
else:
print('⚠️ 不需要認證(可能是開放中繼!)')
# 檢查其他功能
if server.has_extn('SIZE'):
max_size = server.esmtp_features.get('size', 'Unknown')
print(f'📦 最大郵件: {max_size} bytes')
server.quit()
except socket.timeout:
print('❌ 連線逾時')
except socket.error as e:
print(f'❌ 連線錯誤: {e}')
except Exception as e:
print(f'❌ 錯誤: {e}')
print()
# 使用
comprehensive_check('smtp.gmail.com')Q5:應用程式專用密碼和普通密碼有什麼不同?
答案:
核心差異:
普通密碼:
├─ 你的真實密碼
├─ 可以登入所有服務
├─ 權限完整
└─ 無法撤銷(除非改密碼)
應用程式專用密碼:
├─ 系統生成的隨機密碼
├─ 只用於特定應用程式
├─ 權限有限
└─ 可隨時撤銷詳細對比:
| 特性 | 普通密碼 | 應用程式專用密碼 |
|---|---|---|
| 格式 | 用戶自訂 | 系統生成 (16 字元) |
| 記憶 | 需要記住 | 不需記住(儲存) |
| 用途 | 登入所有服務 | 只用於特定應用 |
| 撤銷 | 改密碼影響全部 | 只撤銷該應用 |
| 2FA | 需要輸入驗證碼 | 繞過 2FA |
| 安全性 | 如果外洩影響大 | 只影響該應用 |
| 數量 | 1 個 | 可有多個 |
生成與使用流程:
步驟 1:啟用 2FA
Gmail → 安全性 → 兩步驟驗證 → 啟用
步驟 2:生成應用程式密碼
Gmail → 安全性 → 應用程式密碼
↓
選擇應用類型:「郵件」
選擇裝置:「我的電腦」
↓
生成:abcd efgh ijkl mnop
↓
儲存這個密碼(只顯示一次)
步驟 3:在應用程式中使用
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login('alice@gmail.com', 'abcd efgh ijkl mnop')
└─ 使用應用程式密碼實際範例:
# 錯誤做法:使用真實密碼
server.login('alice@gmail.com', 'MyRealPassword123')
# ❌ 如果啟用 2FA 會失敗
# ❌ 密碼外洩影響所有服務
# 正確做法:使用應用程式密碼
server.login('alice@gmail.com', 'abcd efgh ijkl mnop')
# ✅ 繞過 2FA
# ✅ 外洩只影響這個應用
# ✅ 可隨時撤銷管理多個應用程式密碼:
Gmail 帳戶
├─ 應用程式密碼 1: 「工作電腦 Outlook」
├─ 應用程式密碼 2: 「家裡電腦 Thunderbird」
├─ 應用程式密碼 3: 「Python 腳本」
└─ 應用程式密碼 4: 「手機郵件 App」
好處:
✅ 工作電腦遺失 → 只撤銷密碼 1
✅ Python 腳本外洩 → 只撤銷密碼 3
✅ 不影響其他應用程式撤銷流程:
發現密碼外洩:
1. 進入 Gmail → 安全性 → 應用程式密碼
2. 找到該密碼(例如「Python 腳本」)
3. 點擊「撤銷」
4. 該應用程式立即無法登入
5. 其他應用程式不受影響 ✅
如果是真實密碼外洩:
1. 需要更改密碼
2. 所有登入的裝置都會登出
3. 需要重新登入所有服務 ❌為什麼需要應用程式密碼?
問題場景:
你啟用了 Gmail 2FA
↓
舊版郵件客戶端(Outlook 2010)
↓
不支援 OAuth2
↓
無法完成 2FA 驗證
↓
無法登入 ❌
解決方案:
使用應用程式專用密碼
↓
繞過 2FA(但仍然安全)
↓
舊版客戶端可以登入 ✅安全性分析:
應用程式密碼是否降低安全性?
表面上:
⚠️ 繞過 2FA
⚠️ 不需要驗證碼
實際上:
✅ 密碼由系統生成(強度高)
✅ 可隨時撤銷
✅ 權限有限
✅ 可審計(知道哪個應用在用)
✅ 比真實密碼更安全
風險控制:
如果應用程式密碼外洩
→ 只影響該應用
→ 撤銷該密碼即可
→ 不需要改真實密碼
如果真實密碼外洩
→ 影響所有服務
→ 需要改密碼
→ 所有裝置重新登入最佳實踐:
# 1. 使用環境變數儲存
import os
from dotenv import load_dotenv
load_dotenv()
APP_PASSWORD = os.getenv('GMAIL_APP_PASSWORD')
server.login('alice@gmail.com', APP_PASSWORD)
# 2. 定期輪換
# 每 3-6 個月撤銷舊密碼,生成新密碼
# 3. 明確命名
# 不要用「密碼1」、「密碼2」
# 使用「工作電腦 Outlook」、「自動化腳本」
# 4. 記錄使用場景
# 在密碼管理器中記錄:
# 密碼: abcd efgh ijkl mnop
# 用途: Jenkins CI/CD 發送通知郵件
# 日期: 2025-01-16📝 總結
SMTP 認證演進要點:
歷史脈絡:
1982: 無認證(互信時代)
1990s: 垃圾郵件爆發
1995: SMTP AUTH 誕生
1999: 廣泛部署
2010+: OAuth2、2FA認證方式:
無認證 → 已淘汰 ❌
PLAIN → 需 TLS ⚠️
LOGIN → 需 TLS ⚠️
CRAM-MD5 → 較少用 ⚠️
OAuth2 → 推薦 ✅不需要認證的情況:
✅ 本機測試
✅ 企業內網(IP 白名單)
✅ 伺服器間通訊(MTA to MTA)
✅ 特定場景(白名單 IP)現代最佳實踐:
1. 使用 Port 587 + STARTTLS
2. 強制認證
3. OAuth2(第三方應用)
4. 應用程式密碼(舊版應用)
5. 定期審計記憶口訣:
早期 = 小村莊(互信)
現代 = 大城市(需認證)
無認證 = 危險
PLAIN/LOGIN + TLS = 可用
OAuth2 = 最佳🔗 延伸閱讀
- 上一篇:09-3. IMAP 協定
- 下一篇:10-1. 訊息協定
- RFC 2554 (SMTP AUTH):https://datatracker.ietf.org/doc/html/rfc2554
- RFC 4616 (PLAIN):https://datatracker.ietf.org/doc/html/rfc4616
- RFC 4954 (SMTP AUTH 更新):https://datatracker.ietf.org/doc/html/rfc4954
- Gmail SMTP 設定:https://support.google.com/mail/answer/7126229
- OAuth2 for Gmail:https://developers.google.com/gmail/imap/xoauth2-protocol