Flask-Login将用户ID转为随机字符串而非整数的问题排查
问题根源与解决方案
问题确认
你推测的完全正确:新版本Flask-Security-Too默认会将用户的fs_uniquifier(字符串格式)存入Flask-Login的session字段_user_id中,而非原本的整数用户ID。当Flask-Login从session读取这个字符串ID并传给Flask-Security的find_user方法时,就会触发SQLAlchemy向PostgreSQL传入字符串查询整数类型主键的错误。
无需重构的解决方案
方案1:强制Flask-Security使用用户ID作为身份标识
在初始化Flask-Security之前,添加配置项指定用用户的整数ID作为session中存储的身份标识:
app.config['SECURITY_USER_IDENTITY_ATTRIBUTE'] = 'id'
这个配置会让Flask-Security在用户登录时,将整数user.id存入session的_user_id字段,而非fs_uniquifier,完全匹配你的现有模型结构。
方案2:兼容现有错误session(处理已登录用户)
如果已经有用户的session中存储了fs_uniquifier,可以自定义用户数据存储类,让find_user方法同时兼容两种查询方式:
from flask_security.datastore import SQLAlchemyUserDatastore from psycopg2.errors import InvalidTextRepresentation from sqlalchemy.exc import DataError class CustomUserDatastore(SQLAlchemyUserDatastore): def find_user(self, **kwargs): if 'id' in kwargs: user_identifier = kwargs['id'] # 先尝试按整数ID查询 try: return self.user_model.query.filter_by(id=int(user_identifier)).first() except (ValueError, DataError, InvalidTextRepresentation): # 失败则按fs_uniquifier查询 return self.user_model.query.filter_by(fs_uniquifier=user_identifier).first() # 其他查询条件按原逻辑处理 return super().find_user(**kwargs) # 初始化Flask-Security时使用自定义的datastore user_datastore = CustomUserDatastore(db, User, Role) security = Security(app, user_datastore)
方案3:清除无效session(可选)
为了避免错误事务阻塞后续请求,可以在用户加载时捕获错误并清除无效session:
from flask import session from flask_login import LoginManager from psycopg2.errors import InvalidTextRepresentation from sqlalchemy.exc import DataError login_manager = LoginManager(app) @login_manager.user_loader def load_user(user_id): try: return user_datastore.find_user(id=user_id) except (DataError, InvalidTextRepresentation): # 清除错误的session,让用户重新登录 session.pop('_user_id', None) return None
补充说明
Flask-Security-Too默认切换到fs_uniquifier是为了保证用户身份标识的不可变性(用户ID可能因业务变更修改,但fs_uniquifier生成后固定),但如果你的业务不需要这个特性,方案1是最直接的解决方式,完全不需要修改现有代码和数据库结构。
内容的提问来源于stack exchange,提问作者dadiletta
相关产品推荐
相关产品推荐

