通过链接装饰器使用 Flask-HTTPAuth 创建特权用户 --- 丢失上下文?
Create privileged users with Flask-HTTPAuth by chaining decorators --- losing context?
我正在尝试使用 Flask-HTTPAuth 创建一个具有基本身份验证的两层身份验证系统。我的应用程序有两条路线,一条位于 /
的基本路线可供任何登录用户访问,一条位于 /admin
的管理路线仅供(如您所料)以管理员身份登录的用户访问。
所以我决定通过链接装饰器来实现它,代码的相关部分如下(其中 dbops 只是一个处理与数据库对话的命名空间):
@auth.verify_password
def verify_pw(lastname, password):
ln = lastname.lower()
if ln in dbops.list_users():
hashed_pw = dbops.find_hashed_password(ln)
return bcrypt.checkpw(password.encode('utf8'), hashed_pw.encode('utf8'))
return False
def must_be_admin(f):
@wraps(f)
def wrapper(*args, **kwargs):
if dbops.is_admin(auth.username()):
return f(*args, **kwargs)
return "Not authorized."
return wrapper
@core.route("/")
@auth.login_required
def dataentry():
return render_template("dataentry.html")
@core.route("/admin")
@must_be_admin
@auth.login_required
def admin():
return render_template("admin.html")
只要任何尝试以管理员用户身份登录的人首先访问 /
路由,它就可以正常工作:它会提示输入用户名和密码,然后管理员用户可以转到 /admin
并执行已登录的管理任务。
但是,如果管理员用户首次访问 /admin
,它不会给出登录提示。它只是抛出,在调试器中四处寻找后,我确定 auth.username()
正在返回一个空字符串。所以,我的猜测是由于某种原因,没有应用内部装饰器,因此缺少登录提示。
有人知道这里会发生什么吗?
我的第一个假设是这是一个简单的错误,因为在 is_admin
检查之后才调用 admin 装饰器的内部函数。因此,我试图在检查之前修复我调用函数的问题——因此可能使 auth.username()
可用——如下所示:
def must_be_admin(f):
@wraps(f)
def wrapper(*args, **kwargs):
dummy_to_get_username = f(*args, **kwargs)
if dbops.is_admin(auth.username()):
return dummy_to_get_username
return "Not authorized."
return wrapper
但这只是导致了同样的行为。
我从 了解到库作者推荐的方法是创建两个单独的 Flask-HTTPAuth 对象。我能做的,没问题。但很明显,我关于装饰器如何工作的心智模型失败了,所以我想独立于获得我想要的功能来解决这个问题......
如果不知道装饰器的作用,有时很难确定应用装饰器的正确顺序,但不幸的是,错误的顺序会使应用程序运行不正确。
对于执行某些操作的装饰器 "before" 视图函数运行,在这种情况下,您通常必须按照您希望它们执行的顺序放置装饰器。因此,我认为当您在 must_be_admin
:
之前使用 Flask-HTTPAuth 的 login_required
时,您的代码将达到您的期望
@core.route("/admin")
@auth.login_required
@must_be_admin
def admin():
return render_template("admin.html")
这样,首先会检查凭据,如果缺少或无效 login_required
将 return 向浏览器发送 401 错误,从而出现登录提示。只有在确定凭据有效后,您才需要评估管理装饰器。
我正在尝试使用 Flask-HTTPAuth 创建一个具有基本身份验证的两层身份验证系统。我的应用程序有两条路线,一条位于 /
的基本路线可供任何登录用户访问,一条位于 /admin
的管理路线仅供(如您所料)以管理员身份登录的用户访问。
所以我决定通过链接装饰器来实现它,代码的相关部分如下(其中 dbops 只是一个处理与数据库对话的命名空间):
@auth.verify_password
def verify_pw(lastname, password):
ln = lastname.lower()
if ln in dbops.list_users():
hashed_pw = dbops.find_hashed_password(ln)
return bcrypt.checkpw(password.encode('utf8'), hashed_pw.encode('utf8'))
return False
def must_be_admin(f):
@wraps(f)
def wrapper(*args, **kwargs):
if dbops.is_admin(auth.username()):
return f(*args, **kwargs)
return "Not authorized."
return wrapper
@core.route("/")
@auth.login_required
def dataentry():
return render_template("dataentry.html")
@core.route("/admin")
@must_be_admin
@auth.login_required
def admin():
return render_template("admin.html")
只要任何尝试以管理员用户身份登录的人首先访问 /
路由,它就可以正常工作:它会提示输入用户名和密码,然后管理员用户可以转到 /admin
并执行已登录的管理任务。
但是,如果管理员用户首次访问 /admin
,它不会给出登录提示。它只是抛出,在调试器中四处寻找后,我确定 auth.username()
正在返回一个空字符串。所以,我的猜测是由于某种原因,没有应用内部装饰器,因此缺少登录提示。
有人知道这里会发生什么吗?
我的第一个假设是这是一个简单的错误,因为在 is_admin
检查之后才调用 admin 装饰器的内部函数。因此,我试图在检查之前修复我调用函数的问题——因此可能使 auth.username()
可用——如下所示:
def must_be_admin(f):
@wraps(f)
def wrapper(*args, **kwargs):
dummy_to_get_username = f(*args, **kwargs)
if dbops.is_admin(auth.username()):
return dummy_to_get_username
return "Not authorized."
return wrapper
但这只是导致了同样的行为。
我从
如果不知道装饰器的作用,有时很难确定应用装饰器的正确顺序,但不幸的是,错误的顺序会使应用程序运行不正确。
对于执行某些操作的装饰器 "before" 视图函数运行,在这种情况下,您通常必须按照您希望它们执行的顺序放置装饰器。因此,我认为当您在 must_be_admin
:
login_required
时,您的代码将达到您的期望
@core.route("/admin")
@auth.login_required
@must_be_admin
def admin():
return render_template("admin.html")
这样,首先会检查凭据,如果缺少或无效 login_required
将 return 向浏览器发送 401 错误,从而出现登录提示。只有在确定凭据有效后,您才需要评估管理装饰器。