【问题标题】:Securely implementing two factor authentication in flask在烧瓶中安全地实现两因素身份验证
【发布时间】:2020-06-27 04:10:40
【问题描述】:

我有flask-loginSQLAlchemy 数据库来验证用户登录尝试。但是将用户名和密码输入与基于令牌的 totp 合并一直是我的问题所在。我正在使用passlib 来管理totp 身份验证,该身份验证具有存储在数据库用户模型中的令牌生成器。

我首先想到的是将所有输入字段放在同一页面上。这意味着用户在访问登录页面时需要填写三个字段:用户名、密码、totp 令牌。

但我希望 totp 令牌显示在单独的页面上。所以我 随后将登录页面剪成两页。一个用于用户名和密码,另一个用于 totp 令牌。

我想通过第一页获取用户的登录用户名和密码,然后将其与数据库匹配以查看它们是否正确以及它们在哪里,它们将被路由到带有 totp 令牌输入的另一个页面。此页面将获取令牌输入,并使用从上一页输入的用户生成的数据库令牌进行验证。

正是在为 2fa 创建这样一个单独的路线时,我的所有问题都出现了。例如,

如果他们尚未通过登录页面并成功输入正确的凭据,您必须确保没有人可以访问 2fa 页面。

您还必须将有关谁从登录页面进行身份验证的信息持久保存到 2fa 页面中,以便可以使用 db 中的正确用户验证令牌输入。

而且我不知道如何将谁经历了第一个登录步骤的知识保存到第二个。我首先想到的是在用户成功完成第一次登录过程时使用会话存储来存储用户的 id,以便第二条路由可以获取该会话并识别谁在进行身份验证。但随着饼干的出现。这不是那么安全。

那么如何在 Flask 中安全地实现基于多页令牌的双因素身份验证?

【问题讨论】:

    标签: python flask flask-sqlalchemy flask-login passlib


    【解决方案1】:

    我认为,如果您可以展示您正在使用的一些代码(相关的模型、路由和模板),这可能会有所帮助,以便我们可以更好地为您提供帮助。话虽如此,我认为您正在寻找的关键元素是 Flask 的 session 对象。有了这个,您可以在路由之间保留信息,在本例中为 username

    就像您提到的那样,您不是在一个模板和路由中验证密码和 2fa 令牌,而是将它们一分为二:

    • 验证密码的路径和模板
    • 用于验证 2fa 令牌的路径和模板

    例如,看看下面的两条路由和关联的cmets:

    routes.py

    from flask import session
    
    @app('/login', methods=['GET', 'POST'])
    def login():
        form = LoginForm(request.form)
        status = 200
        if request.method == 'POST' and form.validate():
            user = User.query.filter_by(username=form.username.data).first()
            # set the username in the current session
            session['username'] = user.username
            # check the 2FA is implemented
            if user.otp_secret is None:
                return redirect(url_for('app.two_factor_setup'))
            if user and user.validate(form.password.data): # first only validate the password
                #login_user(user) --> move this to the other route ('two_factor_input' in this example)
                return redirect(url_for('app.two_factor_input'))
            else:
                flash('Wrong credentials')
                status = 401
        return render_template('login.html', form=form), status
    
    
    @app.route('/two_factor_input', methods=['GET', 'POST'])
    def two_factor_input():
        form  = Token2FAForm(request.form)
        status = 200
        if 'username' not in session:
            return redirect(url_for('app.login'))
        user = User.query.filter_by(username=session['username']).first()
        if user is None:
            flash('Something went wrong')
            return redirect(url_for('app.login'))
        if request.method == 'POST':
            if user and user.verify_totp(form.token.data):
                login_user(user)
                flash('Succesvol logged in')
                return redirect(url_for('app.index'))
            else:
                flash('Something went wrong')
                return redirect(url_for('app.two_factor_input'))
    
        return render_template('two_factor_input.html', form=form)
    

    作为“附件”,我附上了一个“用户”模型的示例,如果这可能对您有所帮助。

    PS:我使用 pyotp 模块作为 2fa link

    models.py

    class User(UserMixin, db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        username      = db.Column(db.String(64), unique=True, index=True)
        password_hash = db.Column(db.String(256))
        otp_secret    = db.Column(db.String(16))
    
        def __init__(self, username, password, otp_secret, **kwargs):
            super(User, self).__init__(**kwargs)
            self.username = username
            self.password = password
            self.otp_secret = otp_secret
            if self.otp_secret is None:
                # generate a random secret
                self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
    
        def validate(self, password):
            return check_password_hash(self.password_hash, password)
    
        @property
        def password(self):
            raise AttributeError('password is not a readable attribute')
    
        @password.setter
        def password(self, password):
            self.password_hash = generate_password_hash(password)
    
    
        # methods for 2factor-authentication
        def get_totp_uri(self):
            return 'otpauth://totp/Your App%20Token:{0}?secret={1}&issuer=yourapp'.format(self.username, self.otp_secret)
    
    
        def verify_totp(self, token):
            totp = pyotp.TOTP(self.otp_secret)
            return totp.verify(token)
    

    【讨论】:

      猜你喜欢
      • 2013-07-31
      • 2012-12-02
      • 2015-07-01
      • 2019-07-21
      • 1970-01-01
      • 2014-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多