如何在 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 视图中实现所需的设计

如何在 PHP 中实现装饰器?

如何在 Java 中实现包装装饰器?

装饰器实现flask登录,没登录不让看index页面,多个装饰器装饰一个函数的执行顺序

在 Python 中实现装饰器模式

Flask--登录验证(多个装饰器)