我们正在Flask + Jinja2中编写Web应用程序。 该应用程序已注册用户,可以根据其角色访问某些页面。为了在服务器端实现这一点,我们只使用装饰页面:
@app.route('/action1')
@security_requirements(roles=['some_role'])
def action1():
...
装饰器检查登录用户的角色列表中是否有“some_role”,并决定是将调用传递给装饰函数还是仅将用户重定向到“拒绝访问”页面。
该应用程序还有一个使用bootstrap实现的导航栏。使用基本模板在每个页面中显示导航栏。至于现在,应用程序中的每个页面都在导航栏中有一个条目,无论当前用户是否可以访问它。尽管这不是一个安全漏洞,但我想隐藏他们无法访问的用户页面。此外,我希望在不重复Jinja模板中允许的角色列表的情况下实现此功能。是否可以通过使用我当前的装饰器以某种方式在Jinja中实现此功能?
答案 0 :(得分:5)
我使用Flask-Security,它将很多登录/安全模块绑定在一个很好的包中。它由Flask-Principal提供角色管理,允许您这样做:
{% if current_user.has_role('admin') %}
<li><a href="#">Manage Site</a></li>
{% endif %}
您可see how that's implemented in the source,current_user
代理来自Flask-Login
答案 1 :(得分:2)
我将security_requirements
装饰器更改为如下所示:
def security_requirements(logged_in=True,
roles=None):
def wrapper(f):
# Store the security attributes as a member of the function object
f.access_control = dict(logged_in=logged_in, roles=roles)
@functools.wraps(f)
def wrapped(*args, **kwargs):
access_result = _eval_access(logged_in, roles)
# Redirect the user to the appropriate page (Access denied / Login Required / Actual Page) based on the result
...
与此装饰器的先前版本唯一的真正区别是将安全属性存储在函数对象中的行。这条线在装饰器内部没用。但是,现在我可以实现从Jinja模板调用的以下操作:
{% if can_access(func) %}
<li><a>...</a></li>
{% endif %}
can_access函数在Flask应用程序模块中定义。它接收一个必须转换为函数对象的字符串。它通过调用app.view_functions
:
def can_access(func):
return auth.can_access(app.view_functions[func])
应该直接从Jinja模板调用此函数。所以需要将它添加到Jinja的全局变量中:
app.jinja_env.globals.update(can_access=can_access)
最后,auth.can_access
:
def can_access(f):
if not hasattr(f, 'access_control'):
return True
# Use the access_control member set by the decorator
return _eval_access(**f.access_control) == AccessResult.ALLOWED
此解决方案意味着访问控制在一个地方定义 - 这是功能装饰器。