Автор оригинала: FreeCodeCapm Team.
Грег Обинна
В этом руководстве я покажу вам шаг за шагом подход для структурирования веб-приложения Flask Restplus для тестирования, разработки и производства. Я буду использовать OS на основе Linux (Ubuntu), но большинство этапов могут быть реплицированы на Windows и Mac.
Прежде чем продолжить с этим руководством, у вас должно быть базовое понимание языка программирования Python и Flask Micro Framework. Если вы не знакомы с тем, я рекомендую взглянуть на вступительные статьи – Как использовать Python и Flask, чтобы построить веб-приложение.
Как это руководство структурировано
Это руководство разделено на следующие части:
- Функции
- Что такое флаб-рестораны?
- Настройка и установка
- Настройка и организация проекта
- Настройки конфигурации
- Скрипт колбы
- Модели базы данных и миграция
- Тестирование
- Конфигурация
- Пользовательские операции
- Безопасность и аутентификация
- Защита и авторизация маршрута
- Дополнительные советы
- Расширение приложения и заключения
Функции
Мы будем использовать следующие функции и расширения в нашем проекте.
- Flask-Bcrypt : A Расширение флэки, которое обеспечивает утилиты хеширования BCRYPT для вашего приложения Отказ
- Флэк-мигрировать : Расширение, которое обрабатывает миграции баз данных SQLALCHEMY для приложений в колбе с помощью алемического. Операции базы данных выполнены через интерфейс командной строки Flask или через расширение скрипта Flask-Script.
- Flask-Sqlalchemy : Расширение для Колбу Это добавляет поддержку для SQLalchemy к вашей заявке.
- Pyjwt : Библиотека Python, которая позволяет вам кодировать и декодировать JSON Web Tookens (JWT). JWT является открытым, отраслевым стандартом ( RFC 7519 ) для безопасного представления претензий между двумя сторонами.
- Флэк-скрипт : Расширение, которое обеспечивает поддержку для записи внешних скриптов в колбе и других задачах командной строки, которые принадлежат за пределами самого веб-приложения.
- Пространства имен ( Чертежи )
- Flask-Restplus
- Модульный тест
Что такое флаб-рестораны?
Flask-Restplus – это расширение для колбы, которая добавляет поддержку для быстрого здания API. Flask-Restplus поощряет лучшие практики с минимальной установкой. Он предоставляет когерентную коллекцию декораторов и инструментов для описания вашего API и правильно разоблачить свою документацию (используя чванство).
Настройка и установка
Проверьте, если у вас установлен PIP, набрав команду PIP --version
В терминал, затем нажмите Enter.
pip --version
Если терминал отвечает номером версии, это означает, что PIP установлен, поэтому перейдите на следующий шаг, в противном случае Установите PIP Или используйте менеджер пакета Linux, запустите команду ниже на терминале и нажмите Enter. Выберите либо версию Python 2.x или 3.x.
- Python 2.x.
sudo apt-get install python-pip
- Python 3.x.
sudo apt-get install python3-pip
Настройте виртуальную среду и виртуальную обертку окружающей среды (вам нужно только один из них, в зависимости от версии, установленной выше):
sudo pip install virtualenv sudo pip3 install virtualenvwrapper
Следуйте за Эта ссылка Для полной настройки оболочки виртуальной среды.
Создайте новую среду и активируйте ее, выполняя следующую команду на терминале:
mkproject name_of_your_project
Настройка и организация проекта
Я буду пользоваться Функциональная структура организовать файлы проекта тем, что они делают. В функциональной структуре шаблоны сгруппированы вместе в одном каталоге, статические файлы в другом и представлениях в третьем.
В каталоге проекта создайте новый пакет под названием приложение
Отказ Внутри приложение
Создайте два пакета Главная
и Тест
Отказ Структура вашей каталога должна выглядеть аналогичной ниже.
. ├── app │ ├── __init__.py │ ├── main │ │ └── __init__.py │ └── test │ └── __init__.py └── requirements.txt
Мы собираемся использовать функциональную структуру для модуляции нашего приложения. Внутри Главная
Пакет, создайте еще три пакета, а именно: контроллер
, Сервис
и Модель
Отказ Модель
Пакет будет содержать все наши модели базы данных, пока Сервис
Пакет будет содержать всю бизнес-логику нашего приложения и, наконец, контроллер
Пакет будет содержать все наши конечные точки приложения. Структура дерева теперь должна выглядеть следующим образом:
. ├── app │ ├── __init__.py │ ├── main │ │ ├── controller │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── model │ │ │ └── __init__.py │ │ └── service │ │ └── __init__.py │ └── test │ └── __init__.py └── requirements.txt
Теперь давайте устанавливаем необходимые пакеты. Убедитесь, что созданная вами виртуальная среда активирована и выполняется следующие команды на терминале:
pip install flask-bcrypt pip install flask-restplus pip install Flask-Migrate pip install pyjwt pip install Flask-Script pip install flask_testing
Создать или обновить требования .txt
Файл, запустив команду:
pip freeze > requirements.txt
Сгенерированный требования .txt
Файл должен выглядеть похожим на один ниже:
alembic==0.9.8 aniso8601==3.0.0 bcrypt==3.1.4 cffi==1.11.5 click==6.7 Flask==0.12.2 Flask-Bcrypt==0.7.1 Flask-Migrate==2.1.1 flask-restplus==0.10.1 Flask-Script==2.0.6 Flask-SQLAlchemy==2.3.2 Flask-Testing==0.7.1 itsdangerous==0.24 Jinja2==2.10 jsonschema==2.6.0 Mako==1.0.7 MarkupSafe==1.0 pycparser==2.18 PyJWT==1.6.0 python-dateutil==2.7.0 python-editor==1.0.3 pytz==2018.3 six==1.11.0 SQLAlchemy==1.2.5 Werkzeug==0.14.1
Настройки конфигурации
В Главная
Пакет Создайте файл под названием config.py
. Со следующим контентом:
import os # uncomment the line below for postgres database url from environment variable # postgres_local_base = os.environ['DATABASE_URL'] basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = os.getenv('SECRET_KEY', 'my_precious_secret_key') DEBUG = False class DevelopmentConfig(Config): # uncomment the line below to use postgres # SQLALCHEMY_DATABASE_URI = postgres_local_base DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'flask_boilerplate_main.db') SQLALCHEMY_TRACK_MODIFICATIONS = False class TestingConfig(Config): DEBUG = True TESTING = True SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'flask_boilerplate_test.db') PRESERVE_CONTEXT_ON_EXCEPTION = False SQLALCHEMY_TRACK_MODIFICATIONS = False class ProductionConfig(Config): DEBUG = False # uncomment the line below to use postgres # SQLALCHEMY_DATABASE_URI = postgres_local_base config_by_name = dict( dev=DevelopmentConfig, test=TestingConfig, prod=ProductionConfig ) key = Config.SECRET_KEY
Файл конфигурации содержит три класса настройки среды, которые включают Тестирование
, Развитие
и Производство
Отказ
Мы будем использовать Применение заводской шаблон для создания нашего объекта колбы. Этот шаблон наиболее полезен для создания нескольких экземпляров нашего приложения с разными настройками. Это облегчает легкость, в которой мы переходим между нашим тестированием, разработкой и производственной средой, позвонив create_app
Функция с обязательным параметром.
В __init__.py
Файл внутри Главная
Пакет, введите следующие строки кода:
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from .config import config_by_name db = SQLAlchemy() flask_bcrypt = Bcrypt() def create_app(config_name): app = Flask(__name__) app.config.from_object(config_by_name[config_name]) db.init_app(app) flask_bcrypt.init_app(app) return app
Скрипт колбы
Теперь давайте создадим нашу точку входа в заявку. В корневом каталоге проекта создайте файл под названием Manage.py
Со следующим контентом:
import os import unittest from flask_migrate import Migrate, MigrateCommand from flask_script import Manager from app.main import create_app, db app = create_app(os.getenv('BOILERPLATE_ENV') or 'dev') app.app_context().push() manager = Manager(app) migrate = Migrate(app, db) manager.add_command('db', MigrateCommand) @manager.command def run(): app.run() @manager.command def test(): """Runs the unit tests.""" tests = unittest.TestLoader().discover('app/test', pattern='test*.py') result = unittest.TextTestRunner(verbosity=2).run(tests) if result.wasSuccessful(): return 0 return 1 if __name__ == '__main__': manager.run()
Приведенный выше код внутри Manage.py
делает следующее:
линия 4
и5
Импортирует модули Migrate и Manager соответственно (мы скоро будем использовать команду Migrate).линия 9
называетcreate_app
Функция Мы создали первоначально для создания экземпляра приложения с необходимым параметром из переменной среды, которая может быть одним из следующих –dev
,Продукты
,Тест
Отказ Если никто не установлен в переменной среды, по умолчаниюdev
используется.линия 13
и15
Значения менеджера и миграционных классов, передавприложение
экземпляр к их соответствующим конструкторам.- В
линия 17
мы проходимдБ
иMigratecommand
экземпляры дляadd_command
ИнтерфейсМенеджер
Чтобы открыть все команды миграции базы данных через скрипт Flask. линия 20
и25
Отмечает две функции в качестве исполняемых файлов из командной строки.
На данный момент мы можем проверить приложение, запустив команду ниже в корневом каталоге проекта.
python manage.py run
Если все в порядке, вы должны увидеть что-то вроде этого:
Модели базы данных и миграция
Теперь давайте создадим наши модели. Мы будем использовать дБ
Экземпляр SQLALCHEMY для создания наших моделей.
дБ
Экземпляр содержит все функции и помощники от обоих SQLalchemy и sqlalchemy.orm и Это обеспечивает класс под названием Модель
Это декларативная база, которая может быть использована для объявления моделей.
В Модель
Пакет, создать файл под названием user.py
Со следующим контентом:
from .. import db, flask_bcrypt class User(db.Model): """ User Model for storing user related details """ __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) email = db.Column(db.String(255), unique=True, nullable=False) registered_on = db.Column(db.DateTime, nullable=False) admin = db.Column(db.Boolean, nullable=False, default=False) public_id = db.Column(db.String(100), unique=True) username = db.Column(db.String(50), unique=True) password_hash = db.Column(db.String(100)) @property def password(self): raise AttributeError('password: write-only field') @password.setter def password(self, password): self.password_hash = flask_bcrypt.generate_password_hash(password).decode('utf-8') def check_password(self, password): return flask_bcrypt.check_password_hash(self.password_hash, password) def __repr__(self): return "".format(self.username)
Приведенный выше код внутри user.py
делает следующее:
Строка 3:
Пользователь
Класс наследует отdb.model
Класс, который объявляет класс как модель для SQLalchemy.линия 7
через13
Создает необходимые столбцы для пользовательской таблицы.линия 21
это установка для поляPassword_hash
И это используетFlask-Bcrypt
Чтобы генерировать хеш, используя предоставленный пароль.линия 24
Сравнивает данный пароль с уже сохраненнымPassword_hash
Отказ
Теперь, чтобы генерировать таблицу базы данных из Пользователь
Модель, которую мы только что создали, мы будем использовать Migratecommand
через Менеджер
интерфейс. Для Менеджер
Чтобы обнаружить наши модели, нам придется импортировать Пользователь
Модель, добавив ниже код в Manage.py
файл:
... from app.main.model import user ...
Теперь мы можем приступить к выполнению Миграция Запустив следующие команды на корневом каталоге проекта:
- Инициируйте папку миграции, используя
init
Команда для алемического для выполнения миграций.
python manage.py db init
2. Создайте сценарий миграции от обнаруженных изменений в модели, используя мигрировать
команда. Это еще не влияет на базу данных.
python manage.py db migrate --message 'initial database migration'
3. Примените сценарий миграции в базу данных, используя Обновление
команда
python manage.py db upgrade
Если все успешно работает, у вас должна быть новая база данных SQLite колбу котельной Main.db
Файл, сгенерированный внутри основного пакета.
Тестирование
Конфигурация
Чтобы быть уверенным, что настройка для нашей конфигурации окружающей среды работает, давайте напишем пару тестов для него.
Создайте файл под названием test_config.py
В тестовом пакете с содержанием ниже:
import os import unittest from flask import current_app from flask_testing import TestCase from manage import app from app.main.config import basedir class TestDevelopmentConfig(TestCase): def create_app(self): app.config.from_object('app.main.config.DevelopmentConfig') return app def test_app_is_development(self): self.assertFalse(app.config['SECRET_KEY'] is 'my_precious') self.assertTrue(app.config['DEBUG'] is True) self.assertFalse(current_app is None) self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == 'sqlite:///' + os.path.join(basedir, 'flask_boilerplate_main.db') ) class TestTestingConfig(TestCase): def create_app(self): app.config.from_object('app.main.config.TestingConfig') return app def test_app_is_testing(self): self.assertFalse(app.config['SECRET_KEY'] is 'my_precious') self.assertTrue(app.config['DEBUG']) self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == 'sqlite:///' + os.path.join(basedir, 'flask_boilerplate_test.db') ) class TestProductionConfig(TestCase): def create_app(self): app.config.from_object('app.main.config.ProductionConfig') return app def test_app_is_production(self): self.assertTrue(app.config['DEBUG'] is False) if __name__ == '__main__': unittest.main()
Запустите тест, используя команду ниже:
python manage.py test
Вы должны получить следующий вывод:
Пользовательские операции
Теперь давайте будем работать над следующими операциями, связанными с пользователем:
- Создание нового пользователя
- Получение зарегистрированного пользователя с его
public_id
- Получение всех зарегистрированных пользователей.
Услуги обслуживания пользователя: Этот класс обрабатывает всю логику, относящуюся к модели пользователя. В Сервис
Пакет, создайте новый файл user_service.py
Со следующим контентом:
import uuid import datetime from app.main import db from app.main.model.user import User def save_new_user(data): user = User.query.filter_by(email=data['email']).first() if not user: new_user = User( public_id=str(uuid.uuid4()), email=data['email'], username=data['username'], password=data['password'], registered_on=datetime.datetime.utcnow() ) save_changes(new_user) response_object = { 'status': 'success', 'message': 'Successfully registered.' } return response_object, 201 else: response_object = { 'status': 'fail', 'message': 'User already exists. Please Log in.', } return response_object, 409 def get_all_users(): return User.query.all() def get_a_user(public_id): return User.query.filter_by(public_id=public_id).first() def save_changes(data): db.session.add(data) db.session.commit()
Приведенный выше код внутри user_service.py
делает следующее:
линия 8
через29
Создает новый пользователь путем первой проверки, если пользователь уже существует; Это возвращает успехОтвет_object
Если пользователь не существует еще, он возвращает код ошибки409
и неудачаОтвет_object
Отказлиния 33
и37
Верните список всех зарегистрированных пользователей и объекта пользователя, предоставляяpublic_id
соответственно.линия 40
к42
совершает изменения в базе данных.
В Главная
Пакет, создайте новый пакет под названием Util
Отказ Этот пакет будет содержать все необходимые утилиты, которые нам могут понадобиться в нашем приложении.
В Util
Пакет, создайте новый файл det.py
Отказ Как подразумевает имя, объект передачи данных ( DTO ) будет отвечать за несущие данные между процессами. В нашем собственном случае он будет использоваться для маршаланга данных для наших вызовов API. Мы будем понимать это лучше, когда мы продолжаем.
from flask_restplus import Namespace, fields class UserDto: api = Namespace('user', description='user related operations') user = api.model('user', { 'email': fields.String(required=True, description='user email address'), 'username': fields.String(required=True, description='user username'), 'password': fields.String(required=True, description='user password'), 'public_id': fields.String(description='user Identifier') })
Приведенный выше код внутри det.py
делает следующее:
Линия 5
Создает новое пространство имен для операций, связанных с пользователем. Flask-RestPlus предоставляет способ использовать практически одинаковый шаблон, что и План Отказ Основная идея состоит в том, чтобы разделить приложение в многоразовые пространства имен. Модуль пространства имен будет содержать моделей и декларацию ресурсов.линия 6
Создает новый пользователь DTO черезМодель
Интерфейс, предоставленныйAPI
пространство имен вЛиния 5
Отказ
Контроллер пользователя: Класс контроллера пользователя обрабатывает все входящие HTTP-запросы, относящиеся к пользователю.
Под контроллер
Пакет, создайте новый файл под названием user_controller.py
. Со следующим контентом:
from flask import request from flask_restplus import Resource from ..util.dto import UserDto from ..service.user_service import save_new_user, get_all_users, get_a_user api = UserDto.api _user = UserDto.user @api.route('/') class UserList(Resource): @api.doc('list_of_registered_users') @api.marshal_list_with(_user, envelope='data') def get(self): """List all registered users""" return get_all_users() @api.response(201, 'User successfully created.') @api.doc('create a new user') @api.expect(_user, validate=True) def post(self): """Creates a new User """ data = request.json return save_new_user(data=data) @api.route('/') @api.param('public_id', 'The User identifier') @api.response(404, 'User not found.') class User(Resource): @api.doc('get a user') @api.marshal_with(_user) def get(self, public_id): """get a user given its identifier""" user = get_a_user(public_id) if not user: api.abort(404) else: return user
линия 1
через 8
Импортирует все необходимые ресурсы для контроллера пользователя. Мы определили два бетонных класса в нашем контроллере пользователя, которые являются userList
и Пользователь
Отказ Эти два класса расширяют абстрактный ресурс Flask-Restplus.
API
пространство имен в линия 7
Выше представлен контроллер несколькими декораторами, которые включают, но не ограничиваются следующим:
- API. Маршрут : Декоратор по маршруту ресурсов
- API. Marshal_with : Декоратор, указывающий поля для использования для сериализации (именно здесь мы используем userdto мы создали ранее)
- API. marshal_list_with : Ярлык декоратор для Marshal_with выше с as_list.
- API. Док : Декоратор, чтобы добавить некоторую документацию API в оформленный объект
- API. Ответ: Декоратор, чтобы указать одну из ожидаемых ответов
- API. Ожидайте: Декоратор, чтобы указать ожидаемую модель ввода (мы все еще используем userdto для ожидаемого ввода)
- API. paral: Декоратор, чтобы указать один из ожидаемых параметров
Теперь мы определили наше пространство имен с помощью пользовательского контроллера. Теперь его время, чтобы добавить его в точку записи приложения.
В __init__.py
Файл приложение
Пакет, введите следующее:
# app/__init__.py from flask_restplus import Api from flask import Blueprint from .main.controller.user_controller import api as user_ns blueprint = Blueprint('api', __name__) api = Api(blueprint, title='FLASK RESTPLUS API BOILER-PLATE WITH JWT', version='1.0', description='a boilerplate for flask restplus web service' ) api.add_namespace(user_ns, path='/user')
Приведенный выше код внутри BluePrint.py
делает следующее:
- В
линия 8
мы создаем экземпляр плана, проходяИмя
иImport_name.
API
является основной точкой входа для ресурсов приложений и, следовательно, необходимо инициализировать с помощьюПлан
влиния 10
Отказ - В
линия 16
Добавляем пользовательское пространство именuser_ns
К списку пространств имен вAPI
пример.
Теперь мы определили наш план. Пришло время зарегистрировать его в нашем приложении Flask. Обновить Manage.py
импортируя План
и регистрация его с помощью экземпляра приложения Flask.
from app import blueprint ... app = create_app(os.getenv('BOILERPLATE_ENV') or 'dev') app.register_blueprint(blueprint) app.app_context().push() ...
Теперь мы можем проверить наше приложение, чтобы увидеть, что все работает нормально.
python manage.py run
Теперь откройте URL http://127.0.0.1:5000 в вашем браузере. Вы должны увидеть документацию Swagger.
Давайте проверим Создать новый пользователь Конечная точка с использованием функциональности тестирования SWARGER.
Вы должны получить следующий ответ
Безопасность и аутентификация
Давайте создадим модель BlackListtoken
Для хранения черной списки токенов. В модели
Пакет, создать BlackList.py
Файл со следующим контентом:
from .. import db import datetime class BlacklistToken(db.Model): """ Token Model for storing JWT tokens """ __tablename__ = 'blacklist_tokens' id = db.Column(db.Integer, primary_key=True, autoincrement=True) token = db.Column(db.String(500), unique=True, nullable=False) blacklisted_on = db.Column(db.DateTime, nullable=False) def __init__(self, token): self.token = token self.blacklisted_on = datetime.datetime.now() def __repr__(self): return '
Давайте не забудем мигрировать изменения в силу в нашей базе данных. Импорт Черный список
класс в Manage.py
Отказ
from app.main.model import blacklist
Запустите мигрировать
и Обновление
команды
python manage.py db migrate --message 'add blacklist table' python manage.py db upgrade
Далее создайте BlackList_Service.py
В пакете обслуживания со следующим контентом для черного списка токена:
from app.main import db from app.main.model.blacklist import BlacklistToken def save_token(token): blacklist_token = BlacklistToken(token=token) try: # insert the token db.session.add(blacklist_token) db.session.commit() response_object = { 'status': 'success', 'message': 'Successfully logged out.' } return response_object, 200 except Exception as e: response_object = { 'status': 'fail', 'message': e } return response_object, 200
Обновите Пользователь
Модель с двумя статическими методами кодирования и декодирования токенов. Добавьте следующие импорт:
import datetime import jwt from app.main.model.blacklist import BlacklistToken from ..config import key
- Кодирование
def encode_auth_token(self, user_id): """ Generates the Auth Token :return: string """ try: payload = { 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1, seconds=5), 'iat': datetime.datetime.utcnow(), 'sub': user_id } return jwt.encode( payload, key, algorithm='HS256' ) except Exception as e: return e
- Декодирование: черный список, истекший токен и неверный токен принимается во внимание при декодировании токена аутентификации.
@staticmethod def decode_auth_token(auth_token): """ Decodes the auth token :param auth_token: :return: integer|string """ try: payload = jwt.decode(auth_token, key) is_blacklisted_token = BlacklistToken.check_blacklist(auth_token) if is_blacklisted_token: return 'Token blacklisted. Please log in again.' else: return payload['sub'] except jwt.ExpiredSignatureError: return 'Signature expired. Please log in again.' except jwt.InvalidTokenError: return 'Invalid token. Please log in again.'
Теперь давайте напишем тест на Пользователь
Модель, чтобы убедиться, что наши кодировать
и декодировать
Функции работают должным образом.
В Тест
Пакет, создать base.py
Файл со следующим контентом:
from flask_testing import TestCase from app.main import db from manage import app class BaseTestCase(TestCase): """ Base Tests """ def create_app(self): app.config.from_object('app.main.config.TestingConfig') return app def setUp(self): db.create_all() db.session.commit() def tearDown(self): db.session.remove() db.drop_all()
Baseetescase
Устанавливает нашу тестовую среду, готовую до и после каждого тестового случая, который расширяет его.
Создать test_user_medol.py
С следующими тестовыми случаями:
import unittest import datetime from app.main import db from app.main.model.user import User from app.test.base import BaseTestCase class TestUserModel(BaseTestCase): def test_encode_auth_token(self): user = User( email='test@test.com', password='test', registered_on=datetime.datetime.utcnow() ) db.session.add(user) db.session.commit() auth_token = user.encode_auth_token(user.id) self.assertTrue(isinstance(auth_token, bytes)) def test_decode_auth_token(self): user = User( email='test@test.com', password='test', registered_on=datetime.datetime.utcnow() ) db.session.add(user) db.session.commit() auth_token = user.encode_auth_token(user.id) self.assertTrue(isinstance(auth_token, bytes)) self.assertTrue(User.decode_auth_token(auth_token.decode("utf-8") ) == 1) if __name__ == '__main__': unittest.main()
Запустите тест с Python Manage.py Test
Отказ Все тесты должны пройти.
Давайте создадим Конечные точки аутентификации для Вход и Выход из системы Отказ
- Сначала нам нужен
Dто
для логинской полезной нагрузки. Мы будем использовать Auth Do для@expect
Аннотация вВход
конечная точка. Добавьте код ниже наdo.py.py.py.
class AuthDto: api = Namespace('auth', description='authentication related operations') user_auth = api.model('auth_details', { 'email': fields.String(required=True, description='The email address'), 'password': fields.String(required=True, description='The user password '), })
- Далее мы создаем класс помощника помощников аутентификации для обработки всех операций, связанных с аутентификацией. Это
auth_helper.py
будет в пакете обслуживания и будет содержать два статических метода, которые являютсяlogin_user
иlogout_user.
from app.main.model.user import User from ..service.blacklist_service import save_token class Auth: @staticmethod def login_user(data): try: # fetch the user data user = User.query.filter_by(email=data.get('email')).first() if user and user.check_password(data.get('password')): auth_token = user.encode_auth_token(user.id) if auth_token: response_object = { 'status': 'success', 'message': 'Successfully logged in.', 'Authorization': auth_token.decode() } return response_object, 200 else: response_object = { 'status': 'fail', 'message': 'email or password does not match.' } return response_object, 401 except Exception as e: print(e) response_object = { 'status': 'fail', 'message': 'Try again' } return response_object, 500 @staticmethod def logout_user(data): if data: auth_token = data.split(" ")[1] else: auth_token = '' if auth_token: resp = User.decode_auth_token(auth_token) if not isinstance(resp, str): # mark the token as blacklisted return save_token(token=auth_token) else: response_object = { 'status': 'fail', 'message': resp } return response_object, 401 else: response_object = { 'status': 'fail', 'message': 'Provide a valid auth token.' } return response_object, 403
- Давайте теперь создаем конечные точки для
Вход
иВыход из системы
Операции. В пакете контроллера создайтеauth_controller.py
со следующим содержанием:
from flask import request from flask_restplus import Resource from app.main.service.auth_helper import Auth from ..util.dto import AuthDto api = AuthDto.api user_auth = AuthDto.user_auth @api.route('/login') class UserLogin(Resource): """ User Login Resource """ @api.doc('user login') @api.expect(user_auth, validate=True) def post(self): # get the post data post_data = request.json return Auth.login_user(data=post_data) @api.route('/logout') class LogoutAPI(Resource): """ Logout Resource """ @api.doc('logout a user') def post(self): # get auth token auth_header = request.headers.get('Authorization') return Auth.logout_user(data=auth_header)
- На данный момент осталось единственное, что зарегистрировать Auth
API
пространство имен с приложениемЧертеж
Обновить __init__.py
Файл приложение
пакет со следующим
# app/__init__.py from flask_restplus import Api from flask import Blueprint from .main.controller.user_controller import api as user_ns from .main.controller.auth_controller import api as auth_ns blueprint = Blueprint('api', __name__) api = Api(blueprint, title='FLASK RESTPLUS API BOILER-PLATE WITH JWT', version='1.0', description='a boilerplate for flask restplus web service' ) api.add_namespace(user_ns, path='/user') api.add_namespace(auth_ns)
Запустите приложение с Python Manage.py Run
и откройте URL http://127.0.0.1:5000 в вашем браузере.
Документация Swagger теперь должна отражать вновь созданные auth
пространство имен с Вход
и Выход из системы
конечные точки.
Прежде чем мы пишем некоторые тесты, чтобы обеспечить работу нашей аутентификации, как и ожидалось, давайте изменим нашу регистрационную конечную точку для автоматического войти пользователя после успешной регистрации.
Добавьте метод Generate_token
ниже до user_service.py
:
def generate_token(user): try: # generate the auth token auth_token = user.encode_auth_token(user.id) response_object = { 'status': 'success', 'message': 'Successfully registered.', 'Authorization': auth_token.decode() } return response_object, 201 except Exception as e: response_object = { 'status': 'fail', 'message': 'Some error occurred. Please try again.' } return response_object, 401
Generate_token
Метод генерирует аутентификацию токен Кодируя пользователя я бы.
Это токен это возвращено как ответ.
Далее замените Возвращение Блок в Save_new_user
метод ниже
response_object = { 'status': 'success', 'message': 'Successfully registered.' } return response_object, 201
с участием
return generate_token(new_user)
Теперь пришло время проверить Вход
и Выход из системы
Функциональные возможности. Создать новый тестовый файл test_auth.py
В тестовом пакете со следующим контентом:
import unittest import json from app.test.base import BaseTestCase def register_user(self): return self.client.post( '/user/', data=json.dumps(dict( email='example@gmail.com', username='username', password='123456' )), content_type='application/json' ) def login_user(self): return self.client.post( '/auth/login', data=json.dumps(dict( email='example@gmail.com', password='123456' )), content_type='application/json' ) class TestAuthBlueprint(BaseTestCase): def test_registered_user_login(self): """ Test for login of registered-user login """ with self.client: # user registration user_response = register_user(self) response_data = json.loads(user_response.data.decode()) self.assertTrue(response_data['Authorization']) self.assertEqual(user_response.status_code, 201) # registered user login login_response = login_user(self) data = json.loads(login_response.data.decode()) self.assertTrue(data['Authorization']) self.assertEqual(login_response.status_code, 200) def test_valid_logout(self): """ Test for logout before token expires """ with self.client: # user registration user_response = register_user(self) response_data = json.loads(user_response.data.decode()) self.assertTrue(response_data['Authorization']) self.assertEqual(user_response.status_code, 201) # registered user login login_response = login_user(self) data = json.loads(login_response.data.decode()) self.assertTrue(data['Authorization']) self.assertEqual(login_response.status_code, 200) # valid token logout response = self.client.post( '/auth/logout', headers=dict( Authorization='Bearer ' + json.loads( login_response.data.decode() )['Authorization'] ) ) data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'success') self.assertEqual(response.status_code, 200) if __name__ == '__main__': unittest.main()
Посетите Github Repo для более исчерпывающих тестовых случаев.
Защита и авторизация маршрута
До сих пор мы успешно создали наши конечные точки, реализованные функциональные возможности входа в систему и выходы, но наши конечные точки остаются незащищенными.
Нам нужен способ определить правила, которые определяют, какая из нашей конечной точки открыта или требует аутентификации или даже привилегий администратора.
Мы можем достичь этого, создавая пользовательские декораторы для наших конечных точек.
Прежде чем мы сможем защитить или разрешать любую из наших конечных точек, нам нужно знать текущий вошедший в систему пользователем. Мы можем сделать это, потянув Токен авторизации
От заголовка текущего запроса, используя библиотеку Flask запрос.
Затем мы декодируем детали пользователей от Токен авторизации
Отказ
В Auth
класс auth_helper.py
Файл, добавьте следующий статический метод:
@staticmethod def get_logged_in_user(new_request): # get the auth token auth_token = new_request.headers.get('Authorization') if auth_token: resp = User.decode_auth_token(auth_token) if not isinstance(resp, str): user = User.query.filter_by(id=resp).first() response_object = { 'status': 'success', 'data': { 'user_id': user.id, 'email': user.email, 'admin': user.admin, 'registered_on': str(user.registered_on) } } return response_object, 200 response_object = { 'status': 'fail', 'message': resp } return response_object, 401 else: response_object = { 'status': 'fail', 'message': 'Provide a valid auth token.' } return response_object, 401
Теперь, когда мы можем получить войти в систему пользователя по запросу, давайте пойдем вперед и создать декораторы.
Создать файл декоратор .py
В Util
Пакет со следующим контентом:
from functools import wraps from flask import request from app.main.service.auth_helper import Auth def token_required(f): @wraps(f) def decorated(*args, **kwargs): data, status = Auth.get_logged_in_user(request) token = data.get('data') if not token: return data, status return f(*args, **kwargs) return decorated def admin_token_required(f): @wraps(f) def decorated(*args, **kwargs): data, status = Auth.get_logged_in_user(request) token = data.get('data') if not token: return data, status admin = token.get('admin') if not admin: response_object = { 'status': 'fail', 'message': 'admin token required' } return response_object, 401 return f(*args, **kwargs) return decorated
Для получения дополнительной информации о Декораторы И как их создать, посмотрите на Эта ссылка Отказ
Теперь, когда мы создали декораторы TOKEN_REQUIRED
и admin_token_requireed
Для действительного токена и для токена администратора соответственно все, что осталось, это аннотировать конечные точки, которые мы хотим защитить с оргобзойдерами FreeCodeCamp Декоратор Отказ
Дополнительные советы
В настоящее время для выполнения некоторых задач в нашем приложении мы обязаны для запуска различных команд для запуска приложения, запущенных тестов, устанавливающих зависимости и т. Д. Мы можем автоматизировать эти процессы, устраивая все команды в одном файле, используя Makefile.
В корневом каталоге приложения создайте Makefile
без расширения файла. Файл должен содержать следующее:
.PHONY: clean system-packages python-packages install tests run all clean: find . -type f -name '*.pyc' -delete find . -type f -name '*.log' -delete system-packages: sudo apt install python-pip -y python-packages: pip install -r requirements.txt install: system-packages python-packages tests: python manage.py test run: python manage.py run all: clean install tests run
Вот варианты файла дела.
сделать установку
: Установка как системных пакетов и Python-Packagesсделать чистый
: очищает приложениесделать тесты
: управляет всеми тестамисделать бегать
: запускает приложениесделать все
: ВыполняетУборка
,Установка
, бегитеТесты
иНачинается
приложение.
Расширение приложения и заключения
Довольно легко скопировать текущую структуру приложений и продлить его, чтобы добавить дополнительные функции/конечные точки в приложение. Просто просмотрите любой из предыдущих маршрутов, которые были реализованы.
Не стесняйтесь оставлять комментарий, у вас какие-либо вопросы, наблюдения или рекомендации. Кроме того, если этот пост был полезен для вас, нажмите на значок хлопания, чтобы другие увидят это здесь и пользуются.
Посетите Github Repository для полного проекта.
Спасибо за чтение и удачи!