使用outlook邮箱以smtp方式发送邮件失败的经历

最近在工作用遇到了一个问题,使用outlook邮箱以smtp方式发送邮件失败,报错如下:

bash 复制代码
504 5.7.4 Unrecognized authentication type [TYCP286CA0234.JPNP286.PROD.OUTLOOK.COM 2023-10-28T10:42:16.656Z 08DBD767E4655426]

仅从错误信息来看,是无法识别的身份验证类型

使用其它邮件客户端登录、发送邮件正常,所以密码是没有问题的。

经过一同搜索和AI的帮助,总算搞清楚了这个问题的原因。其原因,是微软已经废弃了AUTH PLAIN(说直白一点,就是明文密码传输),而我这边之前在发送邮件时,使用的是AUTH PLAIN方式。

解决办法其实也很简单,下面给出两个解决方案:

方案1-使用gomail库

这里采用简单粗暴的方式,使用gomail库来发送邮件,

go 复制代码
	from := "******"
	password := "******""
	toEmail := "******"
	smtpHost := "smtp.office365.com"

	m := gomail.NewMessage()
	m.SetHeader("From", from)
	m.SetHeader("To", toEmail)
	m.SetAddressHeader("Cc", from, "Dan")
	m.SetHeader("Subject", "入学通知书")
	m.SetBody("text/html", "请于2020年9月1日前到学校报道:"+time.Now().Local().String())

	d := gomail.NewDialer(smtpHost, 587, from, password)

	// Send the email to Bob, Cora and Dan.
	if err := d.DialAndSend(m); err != nil {
		panic(err)
	}

这个库其实好久没更新了。

吐槽一下:github上其实不少库都是这样,好久没更新了,让人用起来总是不太放心。

方案2-仍使用官方原生库,修改一下AUTH方式

这里采用的是修改AUTH方式的方式,使用的是net/smtp库(这里适应的是参考资料中的方案), 修改后的代码如下:

go 复制代码
package maildemo

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/smtp"
	"testing"
	"text/template"
)

type loginAuth struct {
	username, password string
}

func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte(a.username), nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, errors.New("Unknown from server")
		}
	}
	return nil, nil
}

func TestSendMailV3(t *testing.T) {
	// 设置服务器信息和邮箱账号。
	from := "******"
	password := "******"
	toEmail := "******"

	// Receiver email address.
	to := []string{
		toEmail,
	}

	// smtp server configuration.
	smtpHost := "smtp.office365.com"
	smtpPort := "587"

	conn, err := net.Dial("tcp", "smtp.office365.com:587")
	if err != nil {
		println(err)
	}

	c, err := smtp.NewClient(conn, smtpHost)
	if err != nil {
		t.Error(err)
		return
	}

	tlsconfig := &tls.Config{
		ServerName: smtpHost,
	}

	b, s := c.Extension("PLAIN")

	t.Log(b, s)

	if err = c.StartTLS(tlsconfig); err != nil {
		t.Error(err)
		return
	}

	auth := LoginAuth(from, password)

	if err = c.Auth(auth); err != nil {
		println(err)
	}

	t0, err := template.ParseFiles("template.html")

	if err != nil {
		t.Error(err)
		return
	}

	var body bytes.Buffer

	mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
	body.Write([]byte(fmt.Sprintf("Subject: This is a test subject \n%s\n\n", mimeHeaders)))

	t0.Execute(&body, struct {
		Name    string
		Message string
	}{
		Name:    "Hasan Yousef",
		Message: "This is a test message in a HTML template",
	})

	// Sending email.
	err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Email Sent!")
}

为什么这样改就可以呢?这里不说到SMTP的认证方式。我们可以看到上面的代码在START方法中,返回的第一个参数是LOGIN,而我们看看官方源码中plain auth的Start方法(net/smtp/auth.go):

go 复制代码
func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
	// Must have TLS, or else localhost server.
	// 必须有TLS,否则是本地服务器。
	// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
	// 注意:如果TLS不为true,则我们不能信任ServerInfo中的任何内容。
	// In particular, it doesn't matter if the server advertises PLAIN auth.
	// 特别是,服务器是否告知PLAIN auth都无关紧要。
	// That might just be the attacker saying
	// 那可能是攻击者发的。
	// "it's ok, you can trust me with your password."
	if !server.TLS && !isLocalhost(server.Name) {
		return "", nil, errors.New("unencrypted connection")
	}
	if server.Name != a.host {
		return "", nil, errors.New("wrong host name")
	}
	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
	return "PLAIN", resp, nil
}

我们看到,其返回的第一个参数是PLAIN。这是SMTP支持的验证方式之一,但是安全性不高,所以微软已经废弃了。

我们其实可以在net/smtp代码中打断点,看一下邮件服务器返回的其支持的smtp验证方式,如下图所示(我们这里是在SendMail函数中打断点): 可以看到,其支持的验证方式有:

  • LOGIN
  • XOAUTH2 没有PLAIN,所以我们之前直接用PLAIN AUTH发送时才会报错。

我们再来多一点好奇心,看看其它邮箱服务商的邮件服务器支持的验证方式:

QQ邮箱

smtp服务器地址:smtp.qq.com,端口:587。 可以看到,其支持的验证方式有:

  • LOGIN
  • PLAIN
  • XOAUTH
  • XOAUTH2

新浪邮箱

smtp服务器地址:smtp.sina.com,端口587。 可以看到,其支持的验证方式有:

  • LOGIN
  • PLAIN

参考文档

本文由mdnice多平台发布

相关推荐
为思念酝酿的痛4 小时前
POSIX信号量
linux·运维·服务器·后端
小羊在睡觉4 小时前
力扣84. 柱状图中最大的矩形
后端·算法·leetcode·golang·go
swipe5 小时前
Neo4j + Graph RAG 医疗知识图谱工程实践:患者教育问答真正需要的是“关系可追溯”
后端·langchain·llm
源码宝5 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
金銀銅鐵6 小时前
[Java] 如何理解 class 文件中方法的 descriptor?
java·后端
村口张大爷6 小时前
05 — 分层架构与依赖倒置
后端·架构·系统架构
Jasonakeke7 小时前
SpringBoot自动配置原理揭秘
java·spring boot·后端
IT_陈寒8 小时前
Vite热更新失灵?你可能漏了这个配置
前端·人工智能·后端
uzong8 小时前
面试官:如何做好架构设计
后端·架构
Cosolar9 小时前
QwenPaw Agent 实现原理深度剖析
后端·面试·架构