我正在尝试使用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
检查之后才调用管理员装饰器上的内部函数。 所以我试图在检查之前修复我调用函数---从而可能使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
但这只导致了同样的行为。
我从之前的SO中看到,库作者推荐的方法是创建两个单独的Flask-HTTPAuth对象。 我能做到,没问题。 但很明显,我关于装饰器如何工作的心理模型失败了,所以我想解决这个问题,而不是让我想要的功能工作......
有时很难在不知道装饰器的作用的情况下弄清楚应用装饰器的正确顺序,但不幸的是,错误的顺序会使应用程序行为不正确。
对于在视图函数运行之前执行某些操作的修饰器,在本例中,通常必须按照您希望它们执行的顺序放置装饰器。所以我认为你的代码会在你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
将向浏览器返回401错误,这将使登录提示出现。只有在确定凭据有效后,才要评估管理员修饰器。