通过用户名而非电子邮件进行Flask-Security登录

8

我希望在User模型中添加一个字段,使用户可以通过username而不是email登录。

我定义了:
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'

但是我仍然得到以下结果:

user_datastore.add_role_to_user(name, 'mgmt')
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 105, in add_role_to_user
        user, role = self._prepare_role_modify_args(user, role)
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 72, in _prepare_role_modify_args
        user = self.find_user(email=user)
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 203, in find_user
        return self.user_model.query.filter_by(**kwargs).first()
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 1333, in filter_by
        for key, value in kwargs.items()]
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/base.py", line 383, in _entity_descriptor
        (description, key)
    InvalidRequestError: Entity '<class 'flask_app.models.User'>' has no property 'email'

看起来电子邮件已经硬编码到Flask-Security中了...

我能改变它吗?

编辑: 用户模型(根据评论请求):

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

18

如果您希望使用用户名而不是电子邮件地址进行登录(使用Flask-Security 1.7.0或更高版本),则可以在User模型中将email字段替换为username字段。

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, index=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'))

并更新app配置。

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'

接下来,为了让用户可以使用用户名而不是电子邮件进行登录,我们将利用这一事实: LoginForm验证方法 假定用户的身份属性在 email 表单字段中。

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

通过这种方式,我们可以使用用户名登录,而无需重新编写验证方法或登录模板。当然,这是一种hack的方法,更正确的做法是向ExtendedLoginForm类添加一个自定义的validate方法,该方法检查username表单字段,并相应更新登录模板。

然而,上述方法使得使用用户名或电子邮件地址进行登录变得容易。要实现这个功能,需要定义一个用户模型,其中既有用户名又有电子邮件字段。

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    username = db.Column(db.String(255), unique=True, index=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'))

并更新app配置。

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')

最后,创建自定义登录表单。

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

现在,登录时,Flask-Security将会接受电子邮件地址或用户名填入电子邮件表单字段。


3
我能够通过覆盖登录表单来实现使用用户名或密码进行登录:
class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address')
    username = StringField("Username")

    def validate(self):
    from flask_security.utils import (
        _datastore,
        get_message,
        hash_password,
    )
    from flask_security.confirmable import requires_confirmation
    if not super(LoginForm, self).validate():
        return False

    # try login using email
    self.user = _datastore.get_user(self.email.data)

    if self.user is None:
        self.user = _datastore.get_user(self.username.data)

    if self.user is None:
        self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.password:
        self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.verify_and_update_password(self.password.data):
        self.password.errors.append(get_message("INVALID_PASSWORD")[0])
        return False
    if requires_confirmation(self.user):
        self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
        return False
    if not self.user.is_active:
        self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
        return False
    return True

并按照其他帖子中的描述进行注册:

# Setup Flask-Security
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                login_form=ExtendedLoginForm)

现在电子邮件和用户名都是可选的,可以使用其中一个登录。但是请确保在数据库模型中这两个字段都是唯一的。


1

我该在电子邮件字段中填什么? - Boaz
1
如果您不想使用电子邮件,可以设置一个虚拟的电子邮件。 - itzMEonTV

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接