Skip to content

2. Request в Flask(RESTful API с Flask) и способы аутентификации пользователей

Request в Flask

Request (запрос) - это объект, который содержит всю информацию о входящем HTTP-запросе. Flask автоматически создает этот объект для каждого запроса. Основные атрибуты request:

  • request.method - HTTP метод (GET, POST, PUT, DELETE)
  • request.args - параметры GET запроса
  • request.form - данные формы
  • request.files - загруженные файлы
  • request.json - JSON данные
  • request.headers - HTTP заголовки

Обработка GET запроса

# Импорт необходимых компонентов из Flask
from flask import Flask, request

# Создание экземпляра приложения Flask
app = Flask(__name__)

# Декоратор @app.route определяет URL-маршрут '/search'
# Когда пользователь обращается к этому URL, вызывается функция search()
@app.route('/search')
def search():
    # request.args.get('q', '') получает параметр 'q' из URL
    # Если параметр не указан, возвращается пустая строка
    query = request.args.get('q', '')
    return f'Поисковый запрос: {query}'

Пример 2: Обработка POST запроса с формой

# Создаем словарь с пользователями (в реальном приложении это должна быть база данных)
users_db = {
    'admin': {'password': 'admin123', 'role': 'admin'},
    'user': {'password': 'user123', 'role': 'user'}
}

# Декоратор с указанием метода POST
# methods=['POST'] означает, что этот маршрут обрабатывает только POST запросы
@app.route('/login', methods=['POST'])
def login():
    # request.form.get() получает данные из формы
    # 'username' и 'password' - это имена полей в HTML форме
    username = request.form.get('username')
    password = request.form.get('password')

    # Проверяем существование пользователя
    if username in users_db and users_db[username]['password'] == password:
        return jsonify({
            'message': 'Успешный вход',
            'user': username,
            'role': users_db[username]['role']
        })
    else:
        return jsonify({'error': 'Неверные учетные данные'}), 401

Пример 3: Обработка JSON данных

# Обработка POST запроса с JSON данными
@app.route('/api/data', methods=['POST'])
def receive_data():
    # request.json автоматически парсит JSON данные из тела запроса
    data = request.json
    return f'Получены данные: {data}'

curl — универсальный инструмент командной строки, используемый для передачи данных между клиентом и сервером через различные протоколы. Он поддерживает множество протоколов, таких как HTTP, HTTPS, FTP, SFTP, SMTP, IMAP и другие, используется разработчиками, системными администраторами и тестировщиками.

Описание основных параметров curl

  • -X или --request - указывает HTTP метод запроса (GET, POST, PUT, DELETE)
  • -H или --header - добавляет заголовки HTTP запроса
  • -d или --data - отправляет данные в теле запроса
  • -i или --include - показывает заголовки ответа
  • -v или --verbose - показывает подробную информацию о запросе
  • -b или --cookie - отправляет cookies
  • -c или --cookie-jar - сохраняет cookies в файл
  • -u или --user - указывает данные для базовой аутентификации
  • -k или --insecure - игнорирует проверку SSL-сертификата
  • -L или --location - следует за перенаправлениями

Примеры запросов:

  1. GET запрос к поиску:
# -X GET указывает метод запроса
# URL содержит параметр q=python
curl -X GET "http://localhost:5000/search?q=python"
  1. POST запрос для входа пользователя:
# -H добавляет заголовок Content-Type
# -d отправляет данные формы
curl -X POST "http://localhost:5000/login" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "username=admin&password=admin123"

CRUD операции в Flask

CRUD (Create, Read, Update, Delete) - это базовые операции для работы с данными. Рассмотрим их реализацию на примере простой системы управления задачами.

Создание (Create)

# Создаем список для хранения задач
tasks = []

# Обработка POST запроса для создания новой задачи
@app.route('/tasks', methods=['POST'])
def create_task():
    # Получаем JSON данные из запроса
    task_data = request.json
    # Добавляем новую задачу в список
    tasks.append(task_data)
    # Возвращаем JSON ответ с сообщением и созданной задачей
    return jsonify({'message': 'Задача создана', 'task': task_data})

Чтение (Read)

# Получение списка всех задач
@app.route('/tasks', methods=['GET'])
def get_tasks():
    # Возвращаем все задачи в формате JSON
    return jsonify({'tasks': tasks})

# Получение конкретной задачи по ID
@app.route('/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    # Поиск задачи по ID с помощью генератора
    # next() возвращает первый найденный элемент или None
    task = next((t for t in tasks if t['id'] == task_id), None)
    if task:
        return jsonify(task)
    # Если задача не найдена, возвращаем ошибку 404
    return jsonify({'error': 'Задача не найдена'}), 404

Обновление и удаление (Update, Delete)

# Обновление существующей задачи
@app.route('/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
    # Получаем новые данные задачи
    task_data = request.json
    # Ищем задачу по ID и обновляем её
    for i, task in enumerate(tasks):
        if task['id'] == task_id:
            tasks[i] = task_data
            return jsonify({'message': 'Задача обновлена'})
    return jsonify({'error': 'Задача не найдена'}), 404

# Удаление задачи
@app.route('/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
    # Ищем задачу по ID и удаляем её
    for i, task in enumerate(tasks):
        if task['id'] == task_id:
            del tasks[i]
            return jsonify({'message': 'Задача удалена'})
    return jsonify({'error': 'Задача не найдена'}), 404
  1. Создание новой задачи:
# -H указывает, что отправляем JSON
# -d содержит JSON данные
curl -X POST "http://localhost:5000/tasks" \
     -H "Content-Type: application/json" \
     -d '{"id": 1, "title": "Новая задача", "description": "Описание задачи"}'
  1. Получение списка задач:
# Простой GET запрос без дополнительных параметров
curl -X GET "http://localhost:5000/tasks"
  1. Получение конкретной задачи:
# В URL указываем ID задачи
curl -X GET "http://localhost:5000/tasks/1"
  1. Обновление задачи:
# PUT запрос с JSON данными
curl -X PUT "http://localhost:5000/tasks/1" \
     -H "Content-Type: application/json" \
     -d '{"id": 1, "title": "Обновленная задача", "description": "Новое описание"}'
  1. Удаление задачи:
# DELETE запрос с указанием ID задачи
curl -X DELETE "http://localhost:5000/tasks/1"

RESTful API в Flask

REST (Representational State Transfer) - это архитектурный стиль для создания веб-сервисов. RESTful API следует определенным принципам:

  • Использование HTTP методов для операций
  • Использование URL для идентификации ресурсов
  • Использование JSON для обмена данными
  • Статусные коды HTTP для индикации результата

Базовый RESTful API для пользователей

# Создаем список для хранения пользователей
users = []

# Получение списка всех пользователей
@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify({'users': users})

# Создание нового пользователя
@app.route('/api/users', methods=['POST'])
def create_user():
    # Получаем данные пользователя из JSON
    user_data = request.json
    # Добавляем пользователя в список
    users.append(user_data)
    # Возвращаем созданного пользователя и статус 201 (Created)
    return jsonify(user_data), 201

Создание нового пользователя через API:

# POST запрос с JSON данными пользователя
curl -X POST "http://localhost:5000/api/users" \
     -H "Content-Type: application/json" \
     -d '{"id": 1, "username": "newuser", "email": "newuser@example.com"}'

Получение информации о пользователе:

# GET запрос с ID пользователя
curl -X GET "http://localhost:5000/api/users/1"

Обработка конкретного пользователя

# Получение информации о конкретном пользователе
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    # Поиск пользователя по ID
    user = next((u for u in users if u['id'] == user_id), None)
    if user:
        return jsonify(user)
    # Если пользователь не найден, возвращаем ошибку 404
    return jsonify({'error': 'Пользователь не найден'}), 404

Вложенные ресурсы

# Получение постов конкретного пользователя
@app.route('/api/users/<int:user_id>/posts', methods=['GET'])
def get_user_posts(user_id):
    # Фильтруем посты по ID пользователя
    user_posts = [p for p in posts if p['user_id'] == user_id]
    return jsonify({'posts': user_posts})

Получение постов пользователя:

# GET запрос к вложенному ресурсу
curl -X GET "http://localhost:5000/api/users/1/posts"

Способы аутентификации пользователей

аутентификацией и авторизацией Эти два термина часто путают, так как они тесно связаны с процессом проверки прав доступа пользователя в системе. Однако их функции и цели различны.

Аутентификация (Authentication)

Аутентификация — это процесс подтверждения личности пользователя. Система проверяет, что пользователь действительно тот, за кого себя выдает.

Убедиться, что пользователь является тем, за кого он себя выдает.

Примеры: - Ввод имени пользователя и пароля. - Использование двухфакторной аутентификации (2FA), например, через SMS или приложение для генерации кодов. - Биометрическая аутентификация (отпечаток пальца, распознавание лица). - Аутентификация через OAuth (например, вход через Google или Facebook).


Авторизация (Authorization)

Авторизация — это процесс определения прав доступа пользователя после того, как его личность подтверждена. Система решает, какие ресурсы или действия доступны пользователю.

Определить, что пользователь может делать в системе.

Примеры: - Пользователь с ролью "администратор" может управлять контентом сайта, а пользователь с ролью "гость" — только просматривать его. - Приложение запрашивает доступ к вашим контактам или фотографиям в социальной сети (например, через OAuth). - Доступ к определенным файлам или директориям на сервере.


Реализация в Flask

JWT (JSON Web Token) — это стандарт формата токенов, используемый для безопасной передачи информации между сторонами в виде JSON-объекта. JWT часто применяется для аутентификации и авторизации пользователей в веб-приложениях.

Установка Flask и PyJWT

pip install Flask PyJWT

from flask import Flask, request, jsonify
import jwt
import datetime

app = Flask(__name__)

# Секретный ключ для подписи JWT
SECRET_KEY = "mysecretkey"

# Простая база данных пользователей (в реальном приложении используйте БД)
users = {
    "user1": "password1",
    "user2": "password2"
}

# Функция для создания JWT
def create_jwt(username):
    payload = {
        "username": username,
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)  # Токен действителен 1 час
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

# Функция для проверки JWT
def verify_jwt(token):
    try:
        # Расшифровываем токен
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return payload["username"]
    except jwt.ExpiredSignatureError:
        return "Token expired", 401
    except jwt.InvalidTokenError:
        return "Invalid token", 401

# Маршрут для входа пользователя
@app.route("/login", methods=["POST"])
def login():
    data = request.get_json()
    username = data.get("username")
    password = data.get("password")

    # Проверяем, есть ли пользователь в базе данных
    if username in users and users[username] == password:
        # Создаем JWT
        token = create_jwt(username)
        return jsonify({"token": token}), 200
    else:
        return jsonify({"message": "Invalid credentials"}), 401

# Защищенный маршрут (требует JWT)
@app.route("/protected", methods=["GET"])
def protected():
    # Получаем токен из заголовков запроса
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        return jsonify({"message": "Missing token"}), 401

    # Извлекаем токен из заголовка (Bearer <token>)
    token = auth_header.split(" ")[1]

    # Проверяем токен
    result = verify_jwt(token)
    if isinstance(result, tuple):  # Если вернулась ошибка
        return result

    # Если токен валиден, возвращаем защищенные данные
    return jsonify({"message": f"Hello, {result}! This is a protected route."}), 200

if __name__ == "__main__":
    app.run(debug=True)
  1. Функция create_jwt создает токен, который содержит следующие данные:

    username: имя пользователя. exp: время истечения действия токена (в данном случае 1 час).

Токен подписывается секретным ключом (SECRET_KEY) с использованием алгоритма HMAC SHA-256 (HS256). Подпись гарантирует, что токен не был изменен после его создания. 2. Проверка JWT

Функция verify_jwt проверяет токен:

Если токен истек (ExpiredSignatureError), возвращается ошибка 401.
Если токен невалиден (InvalidTokenError), также возвращается ошибка 401.
Если токен валиден, извлекается имя пользователя.
  1. Маршрут /login принимает логин и пароль пользователя через POST-запрос. Если учетные данные верны, создается JWT и возвращается клиенту.

Проверка через curl

curl -X POST http://127.0.0.1:5000/login \
     -H "Content-Type: application/json" \
     -d '{"username": "user1", "password": "password1"}'

OAuth (Open Authorization) — это стандартный протокол для авторизации, который позволяет пользователям предоставлять доступ к их данным на одном сервисе другому сервису без необходимости делиться учетными данными. В отличие от JWT, OAuth фокусируется на управлении доступом через токены и обычно используется для интеграции с внешними провайдерами (например, Google, Facebook, GitHub).

Установка Flask и Authlib:

pip install Flask Authlib

Для работы с OAuth вам нужно зарегистрировать приложение в выбранном провайдере (например, Google). После регистрации вы получите: - Client ID — уникальный идентификатор вашего приложения. - Client Secret — секретный ключ для вашего приложения.

Пример настройки Google OAuth 1. Перейдите в Google Cloud Console. 2. Создайте проект и включите API "Google OAuth 2.0". 3. Зарегистрируйте ваше приложение, указав URI перенаправления (например, http://localhost:5000/callback). 4. Получите Client ID и Client Secret.

Пример

from flask import Flask, redirect, url_for, session, request, jsonify
from authlib.integrations.flask_client import OAuth
import os

app = Flask(__name__)

# Секретный ключ для сессий
app.secret_key = os.getenv("SECRET_KEY", "supersecretkey")

# Инициализация OAuth
oauth = OAuth(app)

# Настройка Google OAuth
google = oauth.register(
    name="google",
    client_id=os.getenv("GOOGLE_CLIENT_ID", "your-client-id"),
    client_secret=os.getenv("GOOGLE_CLIENT_SECRET", "your-client-secret"),
    access_token_url="https://accounts.google.com/o/oauth2/token",
    authorize_url="https://accounts.google.com/o/oauth2/auth",
    api_base_url="https://www.googleapis.com/oauth2/v1/",
    client_kwargs={"scope": "openid email profile"},
)

# Маршрут для входа через Google
@app.route("/login")
def login():
    redirect_uri = url_for("callback", _external=True)
    return google.authorize_redirect(redirect_uri)

# Маршрут для обработки callback от Google
@app.route("/callback")
def callback():
    token = google.authorize_access_token()
    user_info = google.get("userinfo").json()

    # Сохраняем информацию о пользователе в сессии
    session["user"] = user_info

    return redirect(url_for("profile"))

# Защищенный маршрут (требует авторизации)
@app.route("/profile")
def profile():
    user = session.get("user")
    if not user:
        return redirect(url_for("login"))
    return jsonify(user)

# Маршрут для выхода
@app.route("/logout")
def logout():
    session.pop("user", None)
    return jsonify({"message": "Logged out successfully"})

if __name__ == "__main__":
    app.run(debug=True)

Мы используем библиотеку Authlib для работы с OAuth. В методе oauth.register указываются параметры для подключения к Google OAuth: - client_id и client_secret: данные, полученные при регистрации приложения. - authorize_url: URL для запроса авторизации у пользователя. - access_token_url: URL для получения токена доступа. - api_base_url: базовый URL для API провайдера. - scope: запрашиваемые разрешения (например, доступ к email и профилю).

Маршрут /loginперенаправляет пользователя на страницу авторизации Google. После успешной авторизации Google перенаправит пользователя на маршрут /callback.

Маршрут /callback после авторизации Google отправляет токен доступа на этот маршрут. Мы используем его для получения информации о пользователе (например, email и имя) через API Google.

Маршрут /profile доступен только для авторизованных пользователей. Если пользователь не авторизован, он будет перенаправлен на страницу входа.

Маршрут /logout удаляет информацию о пользователе из сессии, завершая сеанс.

Рекомендации по испольованию данного подхода - OAuth требует использования HTTPS для защиты данных. - Не храните Client ID, Client Secret и другие конфиденциальные данные в коде. Используйте переменные окружения или секретные хранилища. - Добавьте обработку ошибок OAuth (например, если пользователь отклоняет запрос авторизации).


Практические задания

  1. Проверьте из примера про JWT защищенный маршут через curl.
  2. OAuth реазизуйте пример с други провайдером.

Дальнейшие темы для изучения - Куки, сессии - Ролевая модель (Role-Based Access Control, RBAC). - Проверка прав доступа на уровне API (например, middleware в Flask или Django). - ACL (Access Control Lists) для точного контроля доступа. - Подключение к базе данных, SQLAlchemy - Pydentic

Дополнительная литература

Flask для начинающих Официальная документация