Сообщить об ошибке.

Использование базовой аутентификации с маршрутами Flask

ВНИМАНИЕ! Некоторые веб-серверы по умолчанию не передают заголовки авторизации приложению WSGI. Например, если используется Apache с mod_wsgi, то необходимо в файле .htaccess установить опцию WSGIPassAuthorization On. В NGINX все работает из "коробки".

Расширение Flask-HTTPAuth упрощает использование HTTP-аутентификации с маршрутами Flask.

Установка модуля Flask-HTTPAuth в виртуальное окружение.

Так как модуль Flask-HTTPAuth не входит в стандартную библиотеку Python, его необходимо установить отдельно. Cделать это можно с помощью менеджера пакетов pip.

# создаем виртуальное окружение, если нет
$ python3 -m venv .venv --prompt VirtualEnv
# активируем виртуальное окружение 
$ source .venv/bin/activate
# обновляем `pip`
(VirtualEnv):~$ python3 -m pip install -U pip
# ставим модуль `Flask-HTTPAuth`
(VirtualEnv):~$ python3 -m pip install Flask-HTTPAuth

Содержание:

Пример базовой аутентификации

В следующем примере приложения используется базовая проверка подлинности HTTP для защиты корневого маршрута /:

from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
auth = HTTPBasicAuth()

users = {
    "john": generate_password_hash("hello"),
    "susan": generate_password_hash("bye")
}


@auth.verify_password
# функция проверки пароля
def verify_pwd(username, password):
    if (username in users and 
        check_password_hash(users.get(username), password)):
        return username


@app.route('/')
# проверка подлинности для защиты маршрута
@auth.login_required
def index():
    return "Hello, {}!".format(auth.current_user())

if __name__ == '__main__':
    app.run()

Функция verify_pwd(), украшенная декоратором @auth.verify_password, получает имя пользователя username и пароль password, отправленные клиентом. Если учетные данные принадлежат пользователю, то функция должна вернуть объект пользователя. Если учетные данные недействительны, то функция может вернуть None или False. Затем объект пользователя можно запросить из метода auth.current_user() экземпляра аутентификации auth.

Пример дайджест-аутентификации

В следующем примере используется дайджест-аутентификация HTTP:

from flask import Flask
from flask_httpauth import HTTPDigestAuth

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key here'
auth = HTTPDigestAuth()

users = {
    "john": "hello",
    "susan": "bye"
}

@auth.get_password
def get_pw(username):
    if username in users:
        return users.get(username)
    return None

@app.route('/')
# проверка подлинности для защиты маршрута
@auth.login_required
def index():
    return "Hello, {}!".format(auth.username())

if __name__ == '__main__':
    app.run()

Пример аутентификации с помощью токена

В следующем примере используется специальная схема аутентификации HTTP для защиты корневого маршрута / с помощью токена:

from flask import Flask
from flask_httpauth import HTTPTokenAuth

app = Flask(__name__)
auth = HTTPTokenAuth(scheme='Bearer')

tokens = {
    "secret-token-1": "john",
    "secret-token-2": "susan"
}

@auth.verify_token
def verify_token(token):
    if token in tokens:
        return tokens[token]

@app.route('/')
@auth.login_required
def index():
    return "Hello, {}!".format(auth.current_user())

if __name__ == '__main__':
    app.run()

Класс HTTPTokenAuth представляет собой универсальный обработчик аутентификации, который можно использовать с нестандартными схемами аутентификации, при этом имя схемы указывается в качестве аргумента в конструкторе. В приведенном выше примере заголовок WWW-Authenticate, в качестве схемы предоставленный сервером будет использовать 'Bearer':

WWW-Authenticate: Bearer realm="Authentication Required"

Обратный вызов verify_token(token) получает учетные данные аутентификации, предоставленные клиентом в заголовке авторизации. Это может быть простой токен или может содержать несколько аргументов, которые функция verify_token() должна будет проанализировать и извлечь из строки. Как и в случае с verify_password(), функция должна возвращать объект пользователя, если токен действителен.

Использование ролей пользователей

Модуль Flask-HTTPAuth включает простую систему аутентификации на основе ролей, которую можно добавить при необходимости, чтобы обеспечить дополнительный уровень детализации при фильтрации доступа к маршрутам. Чтобы включить поддержку ролей, необходимо написать функцию, возвращающую список ролей для данного пользователя, и украсить ее декоратором auth.get_user_roles:

@auth.get_user_roles
def get_user_roles(user):
    # например, `user.get_roles()` может брать роли из БД 
    return user.get_roles()

Чтобы ПРЕДОСТАВИТЬ доступ к маршруту для пользователей, имеющих заданную роль, необходимо добавить аргумент role в декоратор @auth.login_required:

@app.route('/admin')
# ПУСКАЕМ пользователя с ролью 'admin'
@auth.login_required(role='admin')
def admins_only():
    return f"Hello {auth.current_user()}, you are an admin!"

Аргумент role может принимать список ролей, и в этом случае ДОСТУП БУДЕТ ПРЕДОСТАВЛЕН пользователям, имеющим любую из заданных ролей:

@app.route('/admin')
# ПУСКАЕМ пользователей с ролью 'admin' или 'moderator'
@auth.login_required(role=['admin', 'moderator'])
def moderator():
    return f"Hello {auth.current_user()}, you are an admin or a moderator!"

В наиболее продвинутых случаях пользователей можно фильтровать по нескольким ролям:

@app.route('/admin')
@auth.login_required(role=['user', ['moderator', 'contributor']])
def contributor():
    return f"Hello {auth.current_user()}, you are a user or a moderator/contributor!"

Использование нескольких схем аутентификации

Иногда приложениям необходимо поддерживать комбинацию методов аутентификации. Например, веб-приложение может проверять доступ путем отправки идентификатора и секретного кода клиента поверх базовой аутентификации, в то время как сторонние клиенты API используют токен носителя JWS или JWT. Класс MultiAuth позволяет защитить маршрут с помощью более чем одного объекта аутентификации. Чтобы получить доступ к маршруту, необходимо, чтобы один из методов аутентификации прошел проверку.

from time import time
from flask import Flask
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth
from werkzeug.security import generate_password_hash, check_password_hash
import jwt


app = Flask(__name__)
app.config['SECRET_KEY'] = 'top secret!'

basic_auth = HTTPBasicAuth()
token_auth = HTTPTokenAuth('Bearer')
multi_auth = MultiAuth(basic_auth, token_auth)


users = {
    "john": generate_password_hash("hello"),
    "susan": generate_password_hash("bye")
}

for user in users.keys():
    token = jwt.encode({'username': user, 'exp': int(time()) + 3600},
                       app.config['SECRET_KEY'], algorithm='HS256')
    print('*** token for {}: {}\n'.format(user, token))


@basic_auth.verify_password
# обратный вызов проверки пароля
def verify_password(username, password):
    if username in users:
        if check_password_hash(users.get(username), password):
            return username


@token_auth.verify_token
# обратный вызов проверки токена
def verify_token(token):
    try:
        data = jwt.decode(token, app.config['SECRET_KEY'],
                          algorithms=['HS256'])
    except:  # noqa: E722
        return False
    if 'username' in data:
        return data['username']


@app.route('/')
# защита доступа к маршруту
@multi_auth.login_required
def index():
    return "Hello, %s!" % multi_auth.current_user()


if __name__ == '__main__':
    app.run()

Если нужны роли при использовании MultiAuth:

# псевдокод
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth

basic_auth = HTTPBasicAuth()
token_auth = HTTPTokenAuth(scheme='Bearer')
# в `MultiAuth` передаем экземпляры `basic_auth` и `token_auth`
multi_auth = MultiAuth(basic_auth, token_auth)

...

# ВНИМАНИЕ! Класс `MultiAuth` не имеет метода `.get_user_roles`
# следовательно необходимо использовать следующую конструкцию 
@basic_auth.get_user_roles
@token_auth.get_user_roles
def get_user_roles(user):
    return user.get_roles()

...

@app.route('/admin')
# ПУСКАЕМ пользователей с ролью 'admin' или 'moderator'
@multi_auth.login_required(role=['admin', 'moderator'])
def moderator():
    return f"Hello {auth.current_user()}, you are an ADMIN or a MODERATOR!"

...

API модуля Flask-HTTPAuth

flask_httpauth.HTTPBasicAuth(scheme=None, realm=None):

Класс flask_httpauth.HTTPBasicAuth() обрабатывает базовую аутентификацию HTTP для маршрутов Flask.

Если указан необязательный аргумент scheme, то он будет использоваться вместо стандартной "Basic" схемы в ответе WWW-Authenticate. Довольно распространенной практикой является использование специальной схемы, чтобы браузеры не предлагали пользователю войти в систему.

Аргумент realm можно использовать для предоставления области, определенной приложением, с заголовком WWW-Authenticate.

Методы объекта HTTPBasicAuth

HTTPBasicAuth.verify_password(verify_pwd_callback):

Если функция обратного вызова verify_pwd_callback определена, то она будет вызываться платформой для проверки правильности комбинации имени пользователя и пароля, предоставленной клиентом. Функция обратного вызова принимает два аргумента: username и password и должна вернуть объект пользователя, если учетные данные действительны, или True, если объект пользователя недоступен. В случае неудачной аутентификации она должна вернуть None или False. Пример использования:

@auth.verify_password
def verify_password(username, password):
    user = User.query.filter_by(username).first()
    if user and passlib.hash.sha256_crypt.verify(password, user.password_hash):
        return user

Переданная функция verify_pwd_callback() также вызывается, когда запрос не имеет заголовка авторизации с учетными данными пользователя, и в этом случае аргументам username и password присваиваются пустые строки. В этом случае приложение может вернуть True, и это позволит анонимным пользователям получить доступ к маршруту. Функция обратного вызова verify_pwd_callback() может указать, что пользователь анонимен, записав переменную состояния в flask.g или проверив, имеет ли auth.current_user() значение None.

HTTPBasicAuth.get_user_roles(roles_callback):

Если функция обратного вызова roles_callback определена, то она будет вызываться платформой для получения ролей, назначенных вошедшему пользователю. Функция обратного вызова принимает единственный аргумент - username, для которого запрашиваются роли.

Пользовательский объект username, переданный в эту функцию, будет возвращен обратным вызовом verify_password(). Если обратный вызов проверки вернул True вместо объекта пользователя, то объект авторизации, предоставленный Flask, будет передан в эту функцию. Функция должна возвращать роль или список ролей, принадлежащих пользователю. Пример:

@auth.get_user_roles
def get_user_roles(user):
    return user.get_roles()

HTTPBasicAuth.error_handler(error_callback):

Если функция обратного вызова error_callback определена, то она будет вызываться платформой, когда необходимо отправить ошибку аутентификации обратно клиенту. Функция может принимать один аргумент - код состояния ошибки, который может быть 401 (неправильные учетные данные) или 403 (правильные, но недостаточные учетные данные). Если обратный вызов не предоставлен, то генерируется ответ об ошибке по умолчанию. Пример:

@auth.error_handler
def auth_error(status):
    if status == 403:
        return "<h1>Access Denied</h1>", status
    return "<h1>Error Authenticate</h1>", status

HTTPBasicAuth.login_required(view_callback):

Если функция обратного вызова view_callback определена, то она будет вызвана при успешной аутентификации. Пример:

@app.route('/private')
@auth.login_required
def private_page():
    return "Only for authorized people!"

Можно указать необязательный аргумент role для дальнейшего ограничения доступа по ролям. Пример:

@app.route('/private')
@auth.login_required(role='admin')
def private_page():
    return "Only for admins!"

Необязательный аргумент optional может быть установлен в значение True, чтобы разрешить выполнение маршрута даже в том случае, если аутентификация не включена в запрос, и в этом случае для auth.current_user() будет установлено значение None. Пример:

@app.route('/private')
@auth.login_required(optional=True)
def private_page():
    user = auth.current_user()
    return f"Hello {user.name if user is not None else 'anonymous'}!"

HTTPBasicAuth.current_user():

Пользовательский объект, возвращаемый обратным вызовом verify_password() при успешной аутентификации. Если обратный вызов не возвращает пользователя, то возвращается имя пользователя, переданное клиентом. Пример:

@app.route('/')
@auth.login_required
def index():
    user = auth.current_user()
    return "Hello, {user.name}!"

flask_httpauth.HTTPDigestAuth(scheme=None, realm=None, use_ha1_pw=False, qop='auth', algorithm='MD5'):

Класс flask_httpauth.HTTPDigestAuth() обрабатывает аутентификацию HTTP Digest для маршрутов Flask. чтобы сеанс работал, в приложении Flask должен быть установлен SECRET_KEY. Flask по умолчанию сохраняет пользовательские сеансы в клиенте как защищенные файлы cookie, поэтому клиент должен иметь возможность обрабатывать файлы cookie.

Если указан необязательный аргумент scheme, то он будет использоваться вместо стандартной "Digest" схемы в ответе WWW-Authenticate. Довольно распространенной практикой является использование специальной схемы, чтобы браузеры не предлагали пользователю войти в систему.

Аргумент realm можно использовать для предоставления области, определенной приложением, с заголовком WWW-Authenticate.

Если use_ha1_pw имеет значение False, то обратный вызов get_password должен вернуть простой текстовый пароль для данного пользователя. Если use_ha1_pw имеет значение True, то обратный вызов get_password должен вернуть значение HA1 для данного пользователя. Преимущество установки use_ha1_pw в значение True заключается в том, что это позволяет приложению хранить хэш HA1 пароля в базе данных пользователей.

Аргумент qop настраивает список допустимых расширений качества защиты. Этот аргумент может быть указан в виде строки, разделенной запятыми, списка строк или None для отключения. По умолчанию используется 'auth'.

Аргумент algorithm настраивает используемый алгоритм генерации хеша. По умолчанию - MD5. Реализованы два алгоритма: MD5 и MD5-Sess.

Методы объекта HTTPDigestAuth

  • HTTPBasicAuth.generate_ha1(username, password) генерирует хэш HA1, который можно будет сохранить в базе данных пользователя, если для параметра use_ha1_pw в конструкторе установлено значение True.
  • HTTPDigestAuth.generate_nonce(nonce_making_callback) - если функция обратного вызова nonce_making_callback() определена, то она будет вызываться платформой для генерации nonce. Для этого следует также определить HTTPDigestAuth.verify_nonce(). Можно использовать для механизма хранения состояния, отличного от сеанса.
  • HTTPDigestAuth.verify_nonce(nonce_verify_callback) - если функция обратного вызова nonce_verify_callback() определена, то она будет вызываться платформой для проверки правильности nonce. Она будет вызываться с единственным аргументом: nonce. Можно использовать для механизма хранения состояния, отличного от сеанса.
  • HTTPDigestAuth.generate_opaque(opaque_making_callback) - если функция обратного вызова opaque_making_callback() определена, то она будет вызываться платформой для генерации непрозрачного значения. Для этого следует также определить HTTPDigestAuth.verify_opaque(). Можно использовать для механизма хранения состояния, отличного от сеанса.
  • HTTPDigestAuth.generate_opaque(opaque_making_callback) - если функция обратного вызова opaque_making_callback() определена, то она будет вызываться платформой для проверки допустимости непрозрачного значения. Она будет вызываться с единственным аргументом: opaque, которое необходимо проверить.
  • HTTPDigestAuth.get_user_roles() - аналогична HTTPBasicAuth.get_user_roles().
  • HTTPDigestAuth.error_handler() - аналогична HTTPBasicAuth.error_handler().
  • HTTPDigestAuth.login_required() - аналогична HTTPBasicAuth.login_required().
  • HTTPDigestAuth.current_user() - аналогична HTTPBasicAuth.current_user().

flask_httpauth.HTTPTokenAuth(scheme='Bearer', realm=None, header=None):

Класс flask_httpauth.HTTPTokenAuth() обрабатывает HTTP-аутентификацию с помощью пользовательских схем для маршрутов Flask.

Аргумент scheme можно использовать для указания схемы, которая будет использоваться в ответе WWW-Authenticate. Заголовок авторизации, отправленный клиентом, должен включать эту схему, за которой следует токен. Пример:

Authorization: Bearer this-is-my-token

Аргумент realm можно использовать для предоставления области, определенной приложением, с заголовком WWW-Authenticate.

Аргумент header можно использовать для указания пользовательского заголовка вместо авторизации, откуда можно получить токен. Если используется пользовательский заголовок, то схему включать не следует. Пример:

X-API-Key: this-is-my-token

Методы объекта HTTPTokenAuth

HTTPTokenAuth.verify_token(verify_token_callback)

Если функция обратного вызова verify_token_callback определена, то она будет вызываться платформой для проверки правильности учетных данных, отправленных клиентом с заголовком авторизации. Функция обратного вызова принимает один аргумент - токен token, предоставленный клиентом. Функция должна вернуть объект пользователя, если токен действителен, или True, если объект пользователя недоступен. В случае неудачной аутентификации функция должна вернуть None или False. Пример использования:

@auth.verify_token
def verify_token(token):
    return User.query.filter_by(token=token).first()

Обратите внимание, что при использовании этого класса требуется обратный вызов verify_token().

HTTPTokenAuth.get_user_roles():

Метод HTTPTokenAuth.get_user_roles() аналогичен HTTPBasicAuth.get_user_roles().

HTTPTokenAuth.error_handler():

Метод HTTPTokenAuth.error_handler() аналогичен HTTPBasicAuth.error_handler().

HTTPTokenAuth.login_required():

Метод HTTPTokenAuth.login_required() аналогичен HTTPBasicAuth.login_required().

HTTPTokenAuth.current_user():

Метод HTTPTokenAuth.current_user() аналогичен HTTPBasicAuth.current_user().


flask_httpauth.HTTPMultiAuth(auth_object, ...):

Класс flask_httpauth.HTTPMultiAuth() создаёт объект множественной аутентификации.

Аргументы представляют собой один или несколько экземпляров HTTPBasicAuth, HTTPDigestAuth или HTTPTokenAuth. Маршрут, защищенный этим методом аутентификации, будет проверять все заданные объекты аутентификации, пока ОДИН ИЗ НИХ не достигнет успеха.

Методы объекта HTTPMultiAuth

HTTPMultiAuth.verify_token(verify_token_callback)

Метод HTTPMultiAuth.login_required() аналогичен HTTPBasicAuth.login_required().

HTTPMultiAuth.current_user():

Метод HTTPMultiAuth.current_user() аналогичен HTTPBasicAuth.current_user().