【问题标题】:Slow Flask response when using Flask-security使用 Flask-security 时 Flask 响应缓慢
【发布时间】:2018-04-19 19:19:37
【问题描述】:

在我目前的工作中,我碰巧是一个后端团队的一员,该团队正在创建一个 API。然后 API 应该提供给 JavaScript 应用程序,并且需要非常快(100 毫秒左右)。然而,事实并非如此。

经过一些分析,我们发现是Flask-security 中的令牌身份验证阻碍了我们(请参阅 MWE)。

MWE

import flask
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, auth_required
from flask_sqlalchemy import SQLAlchemy

app = flask.Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/database.sqlite3'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['WTF_CSRF_ENABLED'] = False
app.config['SECURITY_TOKEN_AUTHENTICATION_HEADER'] = 'Authorization'
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_PASSWORD_SALT'] = b'secret'
app.config['SECRET_KEY'] = "super_secret"

db = SQLAlchemy(app)

roles_users = db.Table('roles_users',
                       db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
                       db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))


class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)

# Setup Flask-Security
security = Security(app, user_datastore)

db.drop_all()
db.create_all()

admin_role = Role(**{'name': 'admin', 'description': 'Admin role'})
db.session.add(admin_role)
db.session.commit()

user_datastore.create_user(email='test@example.com', password='test', active=True, roles=[Role.query.first()])
db.session.commit()


@app.route('/')
@auth_required('basic', 'token')
def hello():
    return flask.jsonify({'hello': 'world'})


if __name__ == '__main__':
    app.run(debug=True)

基本认证时机

时间安排是完美的(低于 100 毫秒),但我们不应该这样做。

time curl http://127.0.0.1:5000/ -u "test@example.com:test"
{
  "hello": "world"
}


real    0m0.076s
user    0m0.008s
sys     0m0.006s

令牌认证时机

获取令牌就OK了。

curl -H "Content-Type: application/json" -X POST -d '{"email":"test@example.com","password":"test"}' http://127.0.0.1:5000/login

{
    "meta": {
        "code": 200
    }, 
    "response": {
        "user": {
          "authentication_token": "WyIxIiwiJDUkcm91bmRzPTUzNTAwMCRFRUpLRFNONlB2L1hzL2lRJDhMWFZvZlpLMmVoa1BVdWtpRlhUR1lvNEJ3T3FjS3dKMVhVWGlOczRwZDMiXQ.DOLjcQ.oBrT4gr1m49rISyxhaj9Lxu1VNk", 
          "id": "1"
        }
    }
}

但是请求速度非常慢。 时间慢了 20 倍。

time curl "http://127.0.0.1:5000/?auth_token=WyIxIiwiJDUkcm91bmRzPTUzNTAwMCRFRUpLRFNONlB2L1hzL2lRJDhMWFZvZlpLMmVoa1BVdWtpRlhUR1lvNEJ3T3FjS3dKMVhVWGlOczRwZDMiXQ.DOLjcQ.oBrT4gr1m49rISyxhaj9Lxu1VNk"
{
  "hello": "world"
}

real    0m2.371s
user    0m0.005s
sys     0m0.006s

那是什么???

我知道Flask-security 将其他几个烧瓶安全包(Flask-loginFlask-WTF...)包装在一起。

  1. 您知道可能是什么原因吗? (是Flask-securityFlask-login 还是更深层次的东西?)
  2. 似乎对每个请求都运行缓慢的散列算法。 但是,可能没有必要每次都这样做。 只存储令牌并检查传入的令牌是否与存储的令牌相同就足够了。 有没有办法做到这一点(Flask-security 或没有)?
  3. 我可以为应用程序 (app.config) 设置不同的方式以使其更快(仍然使用令牌身份验证。)?
  4. 是否有解决方法(仍在使用Flask-security)?
  5. 要我自己写吗?是Flask-security 阻碍了我们吗?
  6. 有人知道这件事吗?

我已将此作为问题交叉发布到 GitHub

【问题讨论】:

  • 问得好,但请不要在此处提及截止日期 - 您的听众几乎完全是志愿者,他们会在闲暇时来到这里。
  • @POLOSTutorials:如果您在问题中看到交叉发布声明,请不要将其删除。交叉发布可能被视为有些不耐烦,但未声明的交叉发布更糟糕,即使第一个版本正确拥有交叉发布,OP 也可能会因此而受到反对。
  • KrysotL:我看不到交叉发布的问题,但请确保您链接回此处,以免浪费将来有帮助的人的时间,他们可能会无意中帮助您在其他地方已经解决的问题上。
  • @halfer 感谢您的编辑。 PythonistaCafe 是一个私人论坛,所以我删除了它。但是,我在打开的 github 问题中添加了交叉帖子。
  • 好吧,我们正在远离flask-security(更多细节在github issue comment

标签: python authentication flask flask-login flask-security


【解决方案1】:

我也遇到过这种情况。这是散列,据我所知是 Flask-Security 哲学的一部分。当您更改密码时,令牌将立即失效:我仍然不确定这是否是一个好的要求/功能,但我已经围绕它建立了一个完整的生态系统。由于我有一个处理大量请求的 SPA,我无法忍受每个请求都多花 1 到 3 秒:我也不想使用不太安全的散列方法进行登录。

所以:我在数据库中添加了第二个令牌,有效期为 30 分钟,并且可以更快地验证。

场景:用户使用正常的 Flask-Security 功能登录。一个额外的 REST 端点可用,只能使用慢速令牌访问,它将返回一个新的“快速身份验证令牌”并将其存储在数据库中的 MD5() 表示,并在第二列中设置时间戳。我对所有应用程序级别的 REST 请求使用这个“快速身份验证令牌”,我只是为它添加了一个新的装饰器。

所有用户的东西,如更改密码、更新首选项等,仍由正常登录处理。安全影响较小的 REST 请求通过 Quick-Authentication-Token 进行身份验证。 30 分钟后,SPA 将使用原始身份验证令牌重新检索新的快速身份验证令牌。

我希望它很清楚。可以在此处找到概念证明: https://github.com/acidjunk/improviser/blob/master/improviser/security.py

【讨论】:

  • 随着flask security的fork进入Flask-Security-Too问题也解决了。
【解决方案2】:

身份验证令牌已签名 - 使用 itsdangerous 和您的 SECRET_KEY。因此,Flask-Security(和您)可以确保内容没有被篡改。验证这一点很快。 令牌内部是 user_id 和(已经散列的)用户密码的散列版本。 user_id 不能被认为是唯一的,因为在某些 DB 中,主键值可以重复使用 - 因此 Flask-Security 需要一些唯一性来确定令牌对应于正确的用户。它选择了用户的密码。现在,您不想在令牌中返回经过哈希处理的用户密码(请记住 - 令牌已签名,未加密) - 因此 FS 选择再次对(已哈希处理的)密码进行哈希处理。为了验证令牌,它验证它是由我们签名的,然后提取 user_id 和 hashed-password 并将其与存储在数据库中的密码进行比较。按照设计,密码散列很慢 - 这就是请求缓慢的原因。 沿着@acidjunk 的思路,https://github.com/jwag956/flask-security(我的 Flask-Security 分支)通过简单地在 Usermodel 中添加一个可以充当 uniquifier 的新字段(默认是使用 uuid)实现了一个解决方案。这会产生简单的相等性检查而不是哈希。

【讨论】:

  • 当你还在使用未维护的 Flask-Security 时:请考虑使用这个 fork。它解决了几个错误,得到积极维护,拥有更好的文档并添加了新功能(如 2FA)
【解决方案3】:

值得积极开发的 Flask Security 分支(截至 2019 年 11 月)声称已在 3.3.0 版中解决了此问题:

https://github.com/jwag956/flask-security/blob/master/CHANGES.rst#version-330

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-22
    • 2014-08-16
    • 2021-10-02
    • 2018-09-03
    • 2017-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多