如何解决Fake SMTP服务器不支持SMTP AUTH认证的问题?
解决aiosmtpd模拟SMTP服务器不支持AUTH认证的问题
我编写了一个创建SMTP客户端、登录服务器并发送邮件的函数,想要通过Fake SMTP服务器做单元测试,把邮件内容写入IO流而非实际发送。但登录环节触发以下错误:
smtplib.SMTPNotSupportedError: SMTP AUTH extension not supported by server.
最小可复现示例(MRE)
import email.message import io import smtplib import aiosmtpd.controller import aiosmtpd.handlers import aiosmtpd.smtp def foobar(host, port): client = smtplib.SMTP(host=host, port=port) message = email.message.EmailMessage() message["From"] = "me@me.me" message["To"] = "you@you.you" message["Subject"] = "subject" message.set_content("Hello World") with client as session: session.login("foo", "bar") session.send_message(message) def test_foobar(): io_stream = io.StringIO() handler = aiosmtpd.handlers.Debugging(io_stream) controller = aiosmtpd.controller.Controller(handler) controller.start() foobar(controller.hostname, controller.port) controller.stop() assert "Hello World" in io_stream.getvalue()
依赖要求
pip install aiosmtpd
已尝试的修复方案
添加认证器
按照官方文档说明,aiosmtpd的认证功能默认激活,但需设置有效认证回调才会通过认证,于是添加了认证器:
[...] def test_foobar(): def authenticator(*args, **kwargs): return aiosmtpd.smtp.AuthResult(success=True) [...] controller = aiosmtpd.controller.Controller(handler, authenticator=authenticator) [...]
结果:仍报相同错误
自定义Handler实现认证方法
参考文档,继承Debugging Handler并实现handle_AUTH和具体认证机制方法:
[...] class CustomDebugging(aiosmtpd.handlers.Debugging): async def handle_AUTH(self, *arg, **kwargs): return aiosmtpd.smtp.AuthResult(success=True) async def auth_PLAIN(self, *arg, **kwargs): return aiosmtpd.smtp.AuthResult(success=True) async def auth_LOGIN(self, *arg, **kwargs): return aiosmtpd.smtp.AuthResult(success=True) def test_foobar(): def authenticator(*args, **kwargs): return aiosmtpd.smtp.AuthResult(success=True) handler = CustomDebugging(io_stream) controller = aiosmtpd.controller.Controller( handler, authenticator=authenticator ) [...]
结果:仍报相同错误
解决方案
问题核心是aiosmtpd默认SMTP类不会主动向客户端声明支持AUTH扩展,即便配置了认证器也无效。需要自定义SMTP类,在协议响应中添加AUTH支持声明,同时实现对应认证逻辑。
修改后的完整测试代码如下:
import email.message import io import smtplib import aiosmtpd.controller import aiosmtpd.handlers import aiosmtpd.smtp from aiosmtpd.smtp import AuthResult class AuthEnabledSMTP(aiosmtpd.smtp.SMTP): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 向客户端声明支持PLAIN和LOGIN两种认证机制 self.extensions["AUTH"] = "PLAIN LOGIN" async def auth_PLAIN(self, arg): # 测试场景下直接返回认证成功,可根据需求自定义校验逻辑 return AuthResult(success=True) async def auth_LOGIN(self, arg): # 处理LOGIN认证请求,同样直接返回成功 return AuthResult(success=True) def foobar(host, port): client = smtplib.SMTP(host=host, port=port) message = email.message.EmailMessage() message["From"] = "me@me.me" message["To"] = "you@you.you" message["Subject"] = "subject" message.set_content("Hello World") with client as session: session.login("foo", "bar") session.send_message(message) def test_foobar(): io_stream = io.StringIO() handler = aiosmtpd.handlers.Debugging(io_stream) # 指定控制器使用自定义的支持AUTH的SMTP类 controller = aiosmtpd.controller.Controller( handler, smtp_factory=AuthEnabledSMTP ) controller.start() try: foobar(controller.hostname, controller.port) assert "Hello World" in io_stream.getvalue() finally: controller.stop()
关键说明
- 自定义SMTP类:继承
aiosmtpd.smtp.SMTP,在初始化时给self.extensions添加"AUTH"项,明确告知客户端支持的认证机制(PLAIN和LOGIN是smtplib.login默认尝试的机制)。 - 实现认证方法:必须对应实现
auth_PLAIN和auth_LOGIN方法,返回AuthResult(success=True)来通过认证。 - 指定SMTP工厂:创建Controller时传入
smtp_factory=AuthEnabledSMTP,让服务器使用支持AUTH的SMTP实例。
修改后,客户端调用login时,服务器会正确响应AUTH扩展支持,认证请求会被通过,邮件内容也能正常写入IO流,测试可顺利执行。
内容的提问来源于stack exchange,提问作者Dorian Turba
相关产品推荐
相关产品推荐

