使用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多平台发布

相关推荐
ing6576826 分钟前
同城交易小程序的设计
java·spring boot·后端·mysql·课程设计
洛*璃1 小时前
Spring Cloud微服务项目集成MyBatis
后端·spring·spring cloud·微服务·mybatis·mybatis-plus
知否技术1 小时前
Git系列教程,从小白到大神
git·后端
biubiubiu07061 小时前
SpringBoot+Redis 发布与订阅
spring boot·后端·bootstrap
Mr_兔子先生1 小时前
2024金秋版:Django5开发与部署保姆级零基础教程
后端·python·django
淘源码d6 小时前
医院智慧导诊系统源码,人体画像3D智能导诊系统源码,采用springboot+Uniapp框架开发,支持商用
spring boot·后端·uni-app·源码·导诊·人体导医
Pandaconda8 小时前
【C++ 面试 - 基础题】每日 3 题(一)
开发语言·c++·后端·面试·职场和发展
Pandaconda10 小时前
【Golang 面试 - 进阶题】每日 3 题(十七)
开发语言·后端·面试·职场和发展·golang
岳轩子10 小时前
springboot给类进行赋初值的四种方式
java·spring boot·后端
on the way 12311 小时前
SpringBoot发送QQ邮箱
java·spring boot·后端