在我目前的工作中,我碰巧是后端团队的一员,后者正在创建一个API。然后应该将API提供给JavaScript应用程序,并且需要非常快(100毫秒左右)。但事实并非如此。
经过一些分析后,我们发现它是Flask-security
中的令牌身份验证,它阻止了我们(请参阅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
获取令牌是可以的。
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-login
,Flask-WTF
,...)包装在一起。
Flask-security
还是Flask-login
还是更深层次的东西?)Flask-security
或不使用)? app.config
)以使其更快(仍然使用令牌身份验证。)?Flask-security
)?Flask-security
让我们退缩吗?我已在GitHub上将此问题交叉发布为问题。
答案 0 :(得分:0)
我也遇到了这个问题。就我所知,这是散列,也是Flask-Security哲学的一部分。当您更改密码时,令牌将立即失效:我仍然不确定这是否是一个很好的要求/功能,但是我已经围绕它构建了一个完整的生态系统。因为我有一个SPA,可以处理很多请求,所以我不能忍受每个请求多1-3秒的时间:我也不想使用不太安全的哈希方法进行登录。
所以:我在数据库中添加了第二个令牌,该令牌的有效期为30分钟,并且可以更快地通过验证。
方案:用户使用常规的Flask-Security功能登录。有一个额外的REST终结点,该终结点仅与慢速令牌一起使用,将返回新的“ Quick-Authentication-Token”并将其MD5()表示形式存储在数据库中,并在第二列中设置时间戳。我对所有应用程序级别的REST请求都使用了此“快速身份验证令牌”,只是为其添加了一个新的装饰器。
所有用户内容(如更改密码,更新首选项等)仍由常规登录处理。通过Quick-Authentication-Token对安全性影响较小的REST请求进行身份验证。 30分钟后,SPA将重新获取带有原始身份验证令牌的新的快速身份验证令牌。
我希望这很清楚。概念证明可以在这里找到: https://github.com/acidjunk/improviser/blob/master/improviser/security.py
答案 1 :(得分:0)
使用危险的和您的SECRET_KEY对身份验证令牌进行签名。因此,Flask-Security(和您)可以确信内容没有被篡改。验证速度很快。 令牌内有user_id和用户密码(已哈希)的哈希版本。 user_id不能被认为是唯一的,因为在某些数据库中,主键值可以重用-因此Flask-Security需要一些唯一符,以确保令牌对应于正确的用户。它选择了用户的密码。现在,您不想在令牌中返回经过哈希处理的用户密码(请记住-令牌已签名,未加密)-因此FS选择了再次对(已哈希处理的)密码进行哈希处理。为了验证令牌,它会验证它是否由我们签名,然后取出user_id和hashed-password并将其与数据库中存储的密码进行比较。从设计上讲,密码散列很慢-这就是请求缓慢的原因。 与@acidjunk相似,https://github.com/jwag956/flask-security(我的Flask-Security分支)已经实现了解决方案,只需在Usermodel中添加一个可以用作唯一符的新字段即可(默认是使用uuid)。这样可以进行简单的相等检查,而不是哈希。
答案 2 :(得分:0)
对于积极(截至2019年11月)开发的Flask Security分支而言,它已用3.3.0版本解决了此问题:
https://github.com/jwag956/flask-security/blob/master/CHANGES.rst#version-330