Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Разработка веб-приложений с использованием фреймворка Flask. В 2 частях. Ч.2. Учебное пособие-1.pdf
Скачиваний:
2
Добавлен:
06.09.2026
Размер:
421 Кб
Скачать
☆

3.4. НАСТРОЙКА LOG ROUNDS

Вы, наверное, заметили, что тесты выполняются намного дольше, чем раньше. Это связано с параметром BCRYPT_LOG_ROUNDS пакета Flask Bcrypt, поскольку мы ещё не определили его значение в конфигурации прило-

жения. Flask Bcrypt использует значение по умолчанию 12, что является из-

лишне большим для тестовой среды. Обновим спецификации теста в файле services/users/project/tests/test_config.py:

class TestDevelopmentConfig(TestCase): def create_app(self):

app.config.from_object('project.config.DevelopmentConfig') return app

def test_app_is_development(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertFalse(current_app is None)

self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == os.environ.get('DATABASE_URL')

)

self.assertTrue(app.config['DEBUG_TB_ENABLED']) self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 4)

class TestTestingConfig(TestCase): def create_app(self):

app.config.from_object('project.config.TestingConfig') return app

def test_app_is_testing(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertTrue(app.config['TESTING']) self.assertFalse(app.config['PRESERVE_CONTEXT_ON_EXCEPTION']) self.assertTrue(

app.config['SQLALCHEMY_DATABASE_URI'] == \ os.environ.get('DATABASE_TEST_URL') )

self.assertFalse(app.config['DEBUG_TB_ENABLED']) self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 4)

53

class TestProductionConfig(TestCase): def create_app(self):

app.config.from_object('project.config.ProductionConfig') return app

def test_app_is_production(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertFalse(app.config['TESTING']) self.assertFalse(app.config['DEBUG_TB_ENABLED']) self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 13)

Убедимся, что тесты завершились неудачно, затем обновим файл

services/users/project/config.py:

# services/users/project/config.py

import os

class BaseConfig:

"""Базовая конфигурация"""

DEBUG = False

TESTING = False

SQLALCHEMY_TRACK_MODIFICATIONS = False

SECRET_KEY = 'secret_key'

BCRYPT_LOG_ROUNDS = 13

class DevelopmentConfig(BaseConfig):

"""Конфигурация для разработки"""

DEBUG = True

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') BCRYPT_LOG_ROUNDS = 4

class TestingConfig(BaseConfig):

"""Конфигурация для тестирования"""

DEBUG = True

TESTING = True

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') BCRYPT_LOG_ROUNDS = 4

class ProductionConfig(BaseConfig):

54

"""Конфигурация для производственной среды"""

DEBUG = False

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

Затем обновим файл__init__ в модели User:

class User(db.Model): __tablename__ = "users"

id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(128), unique=True, nullable=False) email = db.Column(db.String(128), unique=True, nullable=False) password = db.Column(db.String(255), nullable=False)

active = db.Column(db.Boolean, default=True, nullable=False)

def __init__(self, username, email, password): self.username = username

self.email = email

self.password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS')

).decode()

И не забудем про обновление импорта:

from flask import current_app

Запустим тесты ещё раз

1.Тесты выполняются успешно.

2.Тесты выполняются быстрее (0,371 против 4,322 с ранее).

Сделайте коммит, а затем отправьте код на GitHub. Убедитесь, что сборка

Travis успешно создаётся.

3.5.ИСПОЛЬЗОВАНИЕ ТОКЕНОВ JWT

Вэтом параграфе мы добавим пакет JWT в сервис user.

Рабочий процесс аутентификации работает следующим образом.

1. Конечный пользователь отправляет учётные данные для входа с кли-

ента в службу users с помощью технологии AJAX.

2. Затем служба users проверяет правильность учётных данных пользо-

вателя и отвечает токеном аутентификации.

3. Токен хранится на клиенте и отправляется со всеми последующими запросами, а служба users декодирует токен и проверяет его.

55

JWT-токены состоят из трёх основных частей:

1)заголовок;

2)полезная нагрузка;

3)подпись.

3.5.1. ПАКЕТ PYJWT

Для работы с веб-токенами формата JSON в приложении добавим пакет

PyJWT в файл requirements.txt:

pyjwt==1.5.3

Добавим следующий тест в TestUserModel() в файл services/users/project/tests /test_user_model.py:

def test_encode_auth_token(self):

user = add_user('justatest', 'test@test.com', 'test')

auth_token = user.encode_auth_token(user.id)

self.assertTrue(isinstance(auth_token, bytes))

Как всегда, убедимся, что тесты успешно не выполняются, а затем доба-

вим метод encode_auth_token в класс User() файла models.py:

def encode_auth_token(self, user_id):

"""Generates the auth token""" try:

payload = {

'exp': datetime.datetime.utcnow() + datetime.timedelta(days=0,\ seconds=5),

'iat': datetime.datetime.utcnow(), 'sub': user_id

}

return jwt.encode( payload,

current_app.config.get('SECRET_KEY'), algorithm='HS256'

)

except Exception as e: return e

Учитывая идентификатор пользователя, метод encode_auth_token кодиру-

ет и возвращает токен. Обратите внимание на полезную нагрузку. Здесь проис-

ходит добавление метаданных о токене и информации о пользователе. Эта ин-

56

формация часто называется «заявлениями» (claims) токенов JWT. Мы исполь-

зовали следующие «заявления»:

1)exp – срок действия токена;

2)iat (issued at) – дата генерации токена;

3)sub – субъект токена, например, – пользователь, которого он иденти-

фицирует.

Добавим следующий импорт:

import datetime

import jwt

Обновим контейнеры:

$ docker-compose -f docker-compose-dev.yml up -d --build

Запустите тесты. Тесты должны выполниться успешно.

Обратимся к конфигурации приложения. Секретный ключ необходимо обновить для производственной среды. Настроим его с помощью переменной окружения.

Сначала в test_config.py изменим все строки вида:

self.assertTrue(app.config['SECRET_KEY'] == 'secret_key')

на:

elf.assertTrue( app.config['SECRET_KEY'] ==

os.environ.get('SECRET_KEY'))

Затем обновим класс BaseConfig в файле services/users/project/config.py:

class BaseConfig:

"""Базовая конфигурация"""

TESTING = False SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = os.environ.get('SECRET_KEY') DEBUG_TB_ENABLED = False

DEBUG_TB_INTERCEPT_REDIRECTS = False

BCRYPT_LOG_ROUNDS = 13

Добавим переменную среды SECRET_KEY в среду в файле docker-

compose-dev.yml:

environment:

-FLASK_DEBUG=1

-APP_SETTINGS=project.config.DevelopmentConfig

-DATABASE_URL=postgres://postgres:postgres@usersdb:5432/users_dev

57

-DATABASE_TEST_URL=postgres://postgres:postgres@users- \ db:5432/users_test

-SECRET_KEY=secret_key

Также добавим в конфигурацию срок действия токена:

class BaseConfig:

"""Базовая конфигурация"""

TESTING = False SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = os.environ.get('SECRET_KEY') DEBUG_TB_ENABLED = False

DEBUG_TB_INTERCEPT_REDIRECTS = False

BCRYPT_LOG_ROUNDS = 13

TOKEN_EXPIRATION_DAYS = 30

TOKEN_EXPIRATION_SECONDS = 0

class DevelopmentConfig(BaseConfig):

"""Конфигурация для разработки"""

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') DEBUG_TB_ENABLED = True

BCRYPT_LOG_ROUNDS = 4

class TestingConfig(BaseConfig):

"""Конфигурация для тестирования"""

TESTING = True

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') BCRYPT_LOG_ROUNDS = 4

TOKEN_EXPIRATION_DAYS = 0

TOKEN_EXPIRATION_SECONDS = 3

class ProductionConfig(BaseConfig):

"""Конфигурация для производственной среды"""

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

С учётом внесённых изменений необходимо обновить тесты:

class TestDevelopmentConfig(TestCase):

def create_app(self):

app.config.from_object('project.config.DevelopmentConfig')

return app

58

def test_app_is_development(self):

self.assertTrue(

app.config['SECRET_KEY'] == os.environ.get('SECRET_KEY')) self.assertFalse(current_app is None)

self.assertTrue(

app.config['SQLALCHEMY_DATABASE_URI'] ==\ os.environ.get('DATABASE_URL')

)

self.assertTrue(app.config['DEBUG_TB_ENABLED']) self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 4) self.assertTrue(app.config['TOKEN_EXPIRATION_DAYS'] == 30) self.assertTrue(app.config['TOKEN_EXPIRATION_SECONDS'] == 0)

class TestTestingConfig(TestCase): def create_app(self):

app.config.from_object('project.config.TestingConfig') return app

def test_app_is_testing(self): self.assertTrue(

app.config['SECRET_KEY'] == os.environ.get('SECRET_KEY')) self.assertTrue(app.config['TESTING']) self.assertFalse(app.config['PRESERVE_CONTEXT_ON_EXCEPTION']) self.assertTrue(

app.config['SQLALCHEMY_DATABASE_URI'] == \ os.environ.get('DATABASE_TEST_URL')

)

self.assertFalse(app.config['DEBUG_TB_ENABLED']) self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 4) self.assertTrue(app.config['TOKEN_EXPIRATION_DAYS'] == 0) self.assertTrue(app.config['TOKEN_EXPIRATION_SECONDS'] == 3)

class TestProductionConfig(TestCase): def create_app(self):

app.config.from_object('project.config.ProductionConfig') return app

59

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]