微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Flask Webapp-注册后验证电子邮件-最佳做法

如何解决Flask Webapp-注册后验证电子邮件-最佳做法

我一直在基本flaskblog上关注documentation。除了Corey的代码外,我还想添加一个逻辑,即用户必须先验证其电子邮件地址,然后才能登录。我想从危险的角度使用URLSafeTimedSerializer来做到这一点,就像Corey Schafer's awesome youtube tutorial所建议的那样。 整个令牌创建和验证过程似乎正常。不幸的是,由于我一般都非常了解python,因此我无法自行找到一种干净的方法将其保存到sqlite3 db中。在我的模型中,我创建了一个布尔列email_confirmed(认值为False),我打算在验证过程后将其更改为True。我的问题是:当用户单击其自定义网址时,如何最好地识别该用户(向谁更改email_confirmed列)?最好将令牌也保存在db列中,然后按该令牌进行过滤以标识用户吗?
以下是一些相关代码

我的模式中的用户类别。py

class User(db.Model,UserMixin):
    id = db.Column(db.Integer,primary_key=True)
    username = db.Column(db.String(20),unique=True,nullable=False)
    email = db.Column(db.String(120),nullable=False)
    image_file = db.Column(db.String(20),nullable=False,default='default_profile.jpg')
    password = db.Column(db.String(60),nullable=False)
    date_registered = db.Column(db.DateTime,default=datetime.utcNow)
    email_confirmed = db.Column(db.Boolean(),default=False)
    email_confirm_date = db.Column(db.DateTime)
    projects = db.relationship('Project',backref='author',lazy=True)


    def get_mail_confirm_token(self,expires_sec=1800):
        s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'],expires_sec)
        return s.dumps(self.email,salt='email-confirm')


    @staticmethod
    def verify_mail_confirm_token(token):
        s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
        try: 
            return s.loads(token,salt='email-confirm',max_age=60)
        except SignatureExpired:
            return "PROBLEM" 

我的路线中的注册逻辑(使用用户蓝图):

@users.route('/register',methods=['GET','POST'])
def register():
    if current_user.is_authenticated: 
        return redirect(url_for('dash.dashboard'))
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user = User(username=form.username.data,email=form.email.data,password=hashed_password)
        db.session.add(user)
        db.session.commit()
        send_mail_confirmation(user)
        return redirect(url_for('users.welcome'))
    return render_template('register.html',form=form)


@users.route('/welcome')
def welcome():
    return render_template('welcome.html')


@users.route('/confirm_email/<token>')
def confirm_email(token):
    user = User.verify_mail_confirm_token(token)
    current_user.email_confirmed = True
    current_user.email_confirm_date = datetime.utcNow 
    return user

最后一部分current_user.email_confirmed = Truecurrent_user.email_confirm_date =datetime.utcNow可能是相关的行。如上所述,由于用户尚未在此阶段登录,所以未进行所需的输入。 我对此表示感谢! 提前非常感谢!

解决方法

您问题的关键是:

我的问题是:当他单击其自定义网址时,如何最好地识别用户(该用户为谁更改email_confirmed列)?

可以看到答案in the example on URL safe serialisation using itsdangerous

令牌本身包含电子邮件地址,因为这就是您在get_mail_confirm_token()函数中使用的地址。

然后,您可以使用序列化程序从该令牌中检索电子邮件地址。您可以在verify_mail_confirm_token()函数中执行此操作,但是,由于它是静态方法,因此您仍然需要会话。您可以将其作为单独的参数传入,尽管没有问题。您还应该处理BadSignature中的itsdangerous异常。然后它将变成:

@staticmethod
def verify_mail_confirm_token(session,token):
    s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
    try: 
        email = s.loads(token,salt='email-confirm',max_age=60)
    except (BadSignature,SignatureExpired):
        return "PROBLEM"

    user = session.query(User).filter(User.email == email).one_or_none()
    return user

将令牌也保存在db列中,然后按该令牌进行过滤以识别用户是否是一个好习惯?

不。令牌应该是短命的,不应保留。

最后,在您的get_mail_confirm_token实现中,您没有正确使用URLSafeTimedSerializer类。您传入了第二个参数expires_sec,但是如果您使用look at the docs,则会看到第二个参数是盐,这可能会导致意想不到的问题。

,

感谢@exhuma。这是我最终使它工作的方式-另外,我还发布了电子邮件发送中以前缺少的部分。

我的模型中的用户类别。py

class User(db.Model,UserMixin):
    id = db.Column(db.Integer,primary_key=True)
    username = db.Column(db.String(20),unique=True,nullable=False)
    email = db.Column(db.String(120),nullable=False)
    image_file = db.Column(db.String(20),nullable=False,default="default_profile.jpg")
    password = db.Column(db.String(60),nullable=False)
    date_registered = db.Column(db.DateTime,default=datetime.utcnow)
    email_confirmed = db.Column(db.Boolean(),default=False)
    email_confirm_date = db.Column(db.DateTime)
    projects = db.relationship("Project",backref="author",lazy=True)

    def get_mail_confirm_token(self):
        s = URLSafeTimedSerializer(
            current_app.config["SECRET_KEY"],salt="email-comfirm"
        )
        return s.dumps(self.email,salt="email-confirm")

    @staticmethod
    def verify_mail_confirm_token(token):
        try:
            s = URLSafeTimedSerializer(
                current_app.config["SECRET_KEY"],salt="email-confirm"
            )
            email = s.loads(token,salt="email-confirm",max_age=3600)
            return email
        except (SignatureExpired,BadSignature):
            return None

在我的utils.py中发送邮件功能

def send_mail_confirmation(user):
    token = user.get_mail_confirm_token()
    msg = Message(
        "Please Confirm Your Email",sender="noreply@demo.com",recipients=[user.email],)
    msg.html = render_template("mail_welcome_confirm.html",token=token)
    mail.send(msg)

routes.py中的注册逻辑(使用用户蓝图):

@users.route("/register",methods=["GET","POST"])
def register():
    if current_user.is_authenticated:
        return redirect(url_for("dash.dashboard"))
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
            "utf-8"
        )
        user = User(
            username=form.username.data,email=form.email.data,password=hashed_password
        )
        db.session.add(user)
        db.session.commit()
        send_mail_confirmation(user)
        return redirect(url_for("users.welcome"))
    return render_template("register.html",form=form)


@users.route("/welcome")
def welcome():
    return render_template("welcome.html")


@users.route("/confirm_email/<token>")
def confirm_email(token):
    email = User.verify_mail_confirm_token(token)
    if email:
        user = db.session.query(User).filter(User.email == email).one_or_none()
        user.email_confirmed = True
        user.email_confirm_date = datetime.utcnow()
        db.session.add(user)
        db.session.commit()
        return redirect(url_for("users.login"))
        flash(
            f"Your email has been verified and you can now login to your account","success",)
    else:
        return render_template("errors/token_invalid.html")
在我看来,

仅丢失是一个简单的条件逻辑,用于在登录前检查email_confirmed = True是否正确,并在confirm_email(token)函数内部进行同样的检查以确保不发生此情况如果用户多次单击确认链接,则该过程可以重复。再次感谢!希望这对其他人有帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。