如何在 Flask 中实现登录所需的装饰器
Posted
技术标签:
【中文标题】如何在 Flask 中实现登录所需的装饰器【英文标题】:How to implement login required decorator in Flask 【发布时间】:2016-04-02 10:15:31 【问题描述】:我有 2 个可以协同工作的 Flask 应用程序(不同的项目)。一个实现了一些使用令牌进行身份验证的 API。第二个使用 API 并为其创建一个 Web 界面。现在我有一个登录函数,它将用户名和密码发送到 API,如果正确,则获取身份验证令牌作为回报。获得令牌后,我将其保存到用户的会话中,现在应该将用户视为已登录/已验证。在这种情况下如何实现 login_required 装饰器。
这是我的登录功能 -
def login(self):
response = make_request(BASE_URL + 'login/', clean_data(self.data))
if response.status_code == 200:
session['auth_token'] = response.json().get('auth_token')
return True
return False
如何制作 login_required 装饰器?
如果这很重要,我也会使用 Redis 来存储会话。
【问题讨论】:
您是想制作自己的装饰器还是使用flask-security之类的东西? 【参考方案1】:查看有关装饰器的官方烧瓶文档: https://flask.palletsprojects.com/en/1.1.x/patterns/viewdecorators/ 或 python 文档 https://www.python.org/dev/peps/pep-0318/ 也是如此。
你的装饰器应该看起来像:
from functools import wraps
from flask import abort
import jwt
def authorize(f):
@wraps(f)
def decorated_function(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
data = request.headers['Authorization'].encode('ascii','ignore')
token = str.replace(str(data), 'Bearer ','')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(user, *args, **kws)
return decorated_function
...然后在你的 app.py 中你可能有:
@app.route('/api/game', methods=['POST'])
@authorize
def create(user):
data = json.loads(request.data)
....
在这种特殊情况下,我使用 JWT 作为令牌,您的令牌可以分别不同,令牌的解码可以是您的自定义实现,但基本机制与上面的示例非常相似。
【讨论】:
【参考方案2】:我会将以下装饰器函数放在常见的地方
def validate_api_token(validation_func):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kws):
api_token = request.headers.get('Authorization')
is_valid_api_token = validation_func(api_token)
if is_valid_api_token:
return f(*args, **kws)
return 'Invalid API Token', 401
return decorated_function
return decorator
对于小型 POC 烧瓶应用,如果您可以将令牌存储在非版本控制文件中,则可以使用以下方法:
# tokens are read from a non-versioned `.tokens` file and loaded into a set
api_tokens = load_api_tokens()
def simple_api_token_validation(api_token):
return api_token in api_tokens
@app.route("/v1/my/secret/function", methods=['POST'])
@validate_api_token(simple_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
另一个简单的选择是查询数据库(例如 redis):
redis_session = Redis(host=REDIS_HOST, password=REDIS_PASSWORD)
def redis_api_token_validation(api_token):
if not api_token:
return False
api_token_hash = hashlib.sha256(api_token.encode()).hexdigest()
return redis_session.exists(f'api:tokens:api_token_hash')
@app.route("/v1/my/secret/function", methods=['POST'])
@validate_api_token(redis_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
@Velin answered 的最佳 IMO 是使用 jwt 来验证令牌
【讨论】:
【参考方案3】:鉴于每个后续请求都将包含 API 令牌,装饰器应执行以下操作
接受一般请求。您可以为此使用 *args 和 **kargs 从标头中提取令牌,并将其与存储在 db 中的令牌进行比较(不是 Redis,而是生成的令牌存储在后端的任何位置) 如果经过身份验证,*args 和 **kargs 应传递给修饰函数 装饰函数的输出应按原样返回 如果身份验证失败,应返回错误消息。有关装饰器的说明,请查看此链接: http://thecodeship.com/patterns/guide-to-python-function-decorators/
【讨论】:
为什么我不应该将令牌与存储在redis中的令牌进行比较?我将在几乎所有视图上使用装饰器,如果我将其与数据库中的值进行比较,我将在每个视图中进行额外的 api 调用。 redis中的token是你从auth server拿到的token,对吧?因此,您应该将其与身份验证服务器中的那个进行比较。 同样不会有任何额外的 API 调用,token 应该是您的 API 请求标头的一部分 我无法理解您要解释的内容。当我发送正确的电子邮件和密码时,身份验证服务器会返回令牌。对于所有其他 api 请求,我必须将身份验证令牌作为标头发送到 api 服务器。 所以当我第一次收到授权令牌时,我将它存储在 redis 中的会话中。以上是关于如何在 Flask 中实现登录所需的装饰器的主要内容,如果未能解决你的问题,请参考以下文章
如何使用装饰器视图在 uicollection 视图中实现所需的设计