Flask-Mail [SSL: WRONG_VERSION_NUMBER] 版本号错误 (_ssl.c:1123)

Flask-Mail [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)

我正在开发一个实现用户注册系统的 Flask 应用程序。该应用程序使用 Flask-Mail 和 itsdangerous 来确认用户的注册并通过电子邮件重置他们的密码。我将 Flask-Mail 配置为使用我正在使用的电子邮件主机提供的推荐服务器设置。

MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True

起初,一切正常;我可以毫无问题地提交电子邮件。然而,似乎没有更改任何配置设置,我现在在尝试使用 Flask-Mail 提交电子邮件时收到以下错误:

[SSL: WRONG_VERSION_NUMBER] 版本号错误 (_ssl.c:1123)

我不确定问题出在哪里,我想知道电子​​邮件提供商是否发生了某些变化?我尝试用 MAIL_USE_SSL=FalseMAIL_USE_TLS=False 设置 MAIL_PORT = 25MAIL_PORT = 465MAIL_USE_SSL=True 以及 MAIL_USE_TLS=False。使用前者,我收到与端口 587 相同的错误,但使用后者,我收到 STARTTLS extension not supported by server.

我是 运行 localhost:5000 处于开发模式的 Flask 应用程序。这是我的一些配置设置和代码:

config.py

    SECRET_KEY = 'verysecret'
    MAIL_SERVER = "smtp.mymailservice.com"
    MAIL_PORT = 587
    MAIL_USE_SSL = False
    MAIL_USE_TLS = True
    MAIL_USERNAME = "myemail@myhostname.com"
    MAIL_PASSWORD = "mypassword"
    MAIL_DEFAULT_SENDER = 'Brand <noreply@myhostname.com>'

app/mailing.py

from flask_mail import Message
from flask import current_app
from .extensions import mail


def send_email(to, subject, template):
    msg = Message(
        subject,
        recipients=[to],
        html=template,
        sender=current_app.config["MAIL_DEFAULT_SENDER"]
    )
    mail.send(msg)

app/users/routes.py

(我收到错误的路线之一)

from flask import (
    render_template, session, request, redirect, url_for, g, jsonify, flash
)

import uuid
from passlib.hash import sha256_crypt

from app.mailing import send_email
from app.extensions import db
from app.users import bp
from app.users.forms import *
from app.users.models import *
from app.users.token import *

@bp.route('/register', methods=['POST', 'GET'])
def register():

    # Initialize the Register Form
    form = RegisterForm()

    # If the submitted form is valid
    if form.validate_on_submit():

        # Check to see if a user already exists with this email address
        user = User.query.filter_by(email=form.email.data).first()

        # If there is not a user with this email address, create a new user
        if not user:
            new_user = User(public_id=str(uuid.uuid4()),
                            email=form.email.data,
                            password=sha256_crypt.encrypt(
                                (form.password.data)),
                            first_name=form.firstname.data,
                            last_name=form.lastname.data

                            )

            db.session.add(new_user)
            db.session.commit()

            token = generate_confirmation_token(new_user.email)
            confirm_url = url_for("users.confirm_email",
                                  token=token, _external=True)
            html = render_template('confirm_email.html',
                                   confirm_url=confirm_url)
            subject = "Please confirm your email"

            try:
                send_email(new_user.email, subject, html)
                flash("A confirmation email has been sent to you. Please verify your email address to activate your account.", category="success")
            except Exception as e:
                flash(
                    "There was a problem sending the confirmation email. Please try again later.", category="danger")
                print(e)

            session["user_id"] = new_user.public_id
            session["email"] = new_user.email
            session["name"] = new_user.first_name

            flash("Thanks for registering!", category="success")

            return redirect(url_for('users.unconfirmed'))
        else:
            flash("There is already an account associated with this email address. Log in, or use a different email address.")

    return render_template("register_user.html", form=form)

app/extensions.py

from flask_mail import Mail
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
bootstrap = Bootstrap()
mail = Mail()

app/init.py

from flask import Flask
from config import Config, DevelopmentConfig

from .errors import (
    page_not_found, forbidden, internal_server_error
)

from .extensions import (
    db, mail, bootstrap
)



def create_app(config_class=DevelopmentConfig):

    app = MyFlask(__name__)

    # Set Configuration
    app.config.from_object(config_class)
    
    # Register extensions
    # Initialize Boostrap-Flask
    bootstrap.init_app(app)

    # Initialize Flask-SQLAlchemy
    db.init_app(app)

    # Initialize Flask-Mail
    mail.init_app(app)

    # Register error views
    app.register_error_handler(404, page_not_found)
    app.register_error_handler(403, forbidden)
    app.register_error_handler(500, internal_server_error)

    with app.app_context():

        # register blueprints
        from app.main import bp as bp_main
        app.register_blueprint(bp_main)

        from app.users import bp as bp_users
        app.register_blueprint(bp_users)

        return app

我明白是怎么回事了。显然,当从 Flask-Mail 初始化 Mail 对象时,您可以为 MAIL_USE_TLS 和 MAIL_USE_SSL 传递非布尔类型。当 Connection 对象调用 configure_host() 并有条件地检查 if self.mail.use_ssl.

时,这会成为一个问题

因此,只要 self.mail.use_ssl 不是 None,该方法就会设置 host = smtplib.SMTP_SSL(self.mail.server, self.mail.port),在我的例子中,这会导致 [SSL: WRONG_VERSION_NUMBER] 错误版本号 (_ssl.c:1123) 因为 mail.port 被设置为 587.

tl;博士 确保将 Flask 应用程序的配置变量设置为适当的类型,尤其是当您使用环境变量时,因为当通过 os.environ dict.

访问它们时,它们的类型始终为 str

flask_mail.py

class Connection(object):
    """Handles connection to host."""

    def __init__(self, mail):
        self.mail = mail

    def __enter__(self):
        if self.mail.suppress:
            self.host = None
        else:
            self.host = self.configure_host()

        self.num_emails = 0

        return self

    def __exit__(self, exc_type, exc_value, tb):
        if self.host:
            self.host.quit()
    
    def configure_host(self):
    ## PROBLEM OCCURRED HERE BECAUSE type(self.mail.use_ssl) = <class 'str'> ##
        if self.mail.use_ssl:
            host = smtplib.SMTP_SSL(self.mail.server, self.mail.port)
        else:
            host = smtplib.SMTP(self.mail.server, self.mail.port)

        host.set_debuglevel(int(self.mail.debug))

        if self.mail.use_tls:
            host.starttls()
        if self.mail.username and self.mail.password:
            host.login(self.mail.username, self.mail.password)

        return host

这个答案几乎已经有了,但对我来说还不够。甚至 TL:DR.

将其放入您的 config.py 文件中,其他的就不用管了...

class Config:
    MAIL_USE_TLS = True
    MAIL_USE_SSL = False

更多详情...

您的 config.py 文件可能如下所示:

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY')
    SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' # os.environ.get('DATABASE_URI')
    MAIL_SERVER = os.environ.get('MAIL_SERVER')
    MAIL_PORT = os.environ.get('MAIL_PORT')
    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS')
    MAIL_USE_SSL = os.environ.get('MAIL_USE_SSL')
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')

然后你认为你可以在你的 VSCode 调试配置或你的服务器环境中有这样的东西:

"env": {
    "MAIL_USE_SSL":"true",
    "MAIL_USE_TLS":"true",

嗯,由于@serrobit 的回答,这不起作用,因为 VSCode 中的“true”变成了 str 而不是 Python True

所以回到开始,在 config.py 文件中将 TLS 硬编码为 True 并将 SSL 硬编码为 False,然后花时间做一些有用的事情。

在您的 config.py 中更改此项:

class Config:
   MAIL_USE_TLS = bool(strtobool(os.environ.get('MAIL_USE_TLS', 'False')))
   MAIL_USE_SSL = bool(strtobool(os.environ.get('MAIL_USE_SSL', 'False')))