python3装饰器顺序_python Flask 装饰器顺序问题解决

本文详细解析了Python3装饰器的执行顺序,并通过Flask框架中的路由装饰器`@route`和权限装饰器`@login_required`为例,阐述了为何必须保证`route`装饰器在最顶层。分析了错误的装饰器顺序导致的未授权访问漏洞,通过实例解释了正确和错误的装饰器使用方式。
摘要由CSDN通过智能技术生成

上周 RealWorld CTF 2018 web 题 bookhub 有个未授权访问的漏洞,比较有意思,赛后看了一下公开的 WriteUp,大家也都没写清楚,所以就有了这篇博文。

前言

这个题是用 flask 框架写的,在www/bookhub/views/user.py中,refresh_session方法存在未授权访问漏洞,代码是这样写的:

@login_required

@user_blueprint.route('/admin/system/refresh_session/', methods=['POST'])

def refresh_session():

pass # 这里省略内容

注意看 @login_required这个装饰器写在了 route装饰器上面了,导致了 login_required未调用。那么,为什么会这样子呢?

官方文档

Flask 官方文档中关于Login Required Decorator说明 这一节里面有一行说明:

To use the decorator, apply it as innermost decorator to a view function. When applying further decorators, always remember that the route() decorator is the outermost.

大概意思就是,必须保证 route 装饰器在最顶层

那么为什么要这样提示呢?

Python 装饰器顺序说明

本节内容可直接参考: Python 装饰器执行顺序迷思

总结一下就是,装饰的顺序按靠近函数顺序执行,从内到外装饰,调用时由外而内,执行顺序和装饰顺序相反。

回过头来看 Flask

Flask 框架中, route装饰器是这么写的:

def route(self, rule, **options):

"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the

:func:`url_for` function is prefixed with the name of the blueprint.

"""

def decorator(f):

endpoint = options.pop("endpoint", f.__name__)

self.add_url_rule(rule, endpoint, f, **options)

return f

return decorator

route 调用了add_url_rule, 对传入的f添加一条 URL 规则。

所以,按照 python 装饰器顺序:

如果@app.route在内层,那么就会把最原始的 view 函数传给 add_url_rule , Flask 框架就会添加一条 URL 规则,指向最原始的 view 函数。

如果 @app.route在外层,那么就会把已经被 login_required 装饰过的 view 函数传给 add_url_rule , Flask 框架就会添加一条 URL 规则,指向已经装饰过的 view 函数。

下面是两个例子,来说明:

正确写法

@user_blueprint.route('/admin/refresh_session/', methods=['POST'])

@login_required

def refresh_session():

pass

这段代码相当于:

# 这里没有装饰器

def refresh_session():

pass

login_wrapped = login_required(refresh_session) # login 装饰器

both_wrapped = app.route('/admin/refresh_session/')(login_wrapped) # route 装饰器

/admin/refresh_session/这条路由指向的实际是login_wrapped,这样就会经过 login 检查

错误写法

@login_required

@user_blueprint.route('/admin/refresh_session/', methods=['POST'])

def refresh_session():

pass

这段代码相当于:

# 这里没有装饰器

def refresh_session():

pass

route_wrapped = app.route('/admin/refresh_session/')(refresh_session) # route 装饰器

login_wrapped = login_required(route_wrapped) # login 装饰器

/admin/refresh_session/这条路由指向的实际是refresh_session , 而login_wrapped 并没有与路由挂勾,所以不会被调用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持聚米学院。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值