В Части 1 мы создали базовый каркас для готового к производству Flask API. Но что такое API без данных, и если мы обслуживаем конфиденциальные данные, как нам их защитить? Легкий REST с Flask, часть 2 посвящена данным и безопасности. Следуйте пошаговым инструкциям, пока мы подключаем наш API к действующей базе данных и защищаем наши конечные точки. Напоминаем, что вы можете получить доступ к коду из репозитория git здесь.
Давайте начнем!
Подключение базы данных
Чтобы упростить задачу, мы воспользуемся Heroku для развертывания базы данных PostgreSQL. Конечно, ваше приложение может использовать любую строку подключения к базе данных и не привязано к Heroku, но Heroku предлагает бесплатное приложение вместе с бесплатным надстройкой PostgreSQL. Выполните следующие общие шаги, чтобы настроить Heroku:
1. Зарегистрируйтесь в Heroku / войдите в свою учетную запись Heroku
2. Создайте новое приложение.
3. После создания настройте / добавьте надстройку «Heroku Postgres» с помощью плана Hobby Dev (бесплатно).
4. Добавьте Heroku CLI в свой терминал.
После того, как все настроено, получить строку подключения к базе данных Heroku так же просто, как
heroku config:get DATABASE_URL -a {your_heroku_app_name}
Результатом этой команды будет то, что мы дадим нашему плагину Flask SqlAlchemy в качестве строки подключения. Чтобы упростить эту задачу, добавьте указанную выше команду в tasks.py
скрипт, чтобы вы всегда получали DATABASE_URL перед запуском приложения Flask. Вам также нужно будет добавить свой HEROKU_APP_NAME
вверху tasks.py
.
Подключение к базе данных
Теперь, когда мы можем запустить наш сервер, давайте посмотрим, как мы можем подключить нашу базу данных в chap-2/app/__init__.py
.
Мы определяем переменную db
, а затем внутри фабричной функции инициализируем базу данных с приложением. Поскольку мы уже настроили нашу конфигурацию для включения переменной SQLALCHEMY_DATABASE_URI, мы можем использовать эту простую команду для подключения нашего приложения к базе данных.
Определение моделей
Что такое база данных без сущностей и моделей? Давайте посмотрим на модель пользователя в app/models/user.py
.
"User Model"
# pylint: disable=no-member
from app import db
from sqlalchemy.sql import func
# A generic user model that might be used by an app powered by flask-praetorian
class User(db.Model):
__tablename__ = "users"
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
roles = db.Column(db.Text)
is_active = db.Column(db.Boolean, default=True, server_default="true")
created_datetime = db.Column(
db.DateTime(), nullable=False, server_default=func.now()
)
@property
def rolenames(self):
try:
return self.roles.split(",")
except Exception:
return []
@classmethod
def lookup(cls, username: str):
return cls.query.filter_by(username=username).one_or_none()
@classmethod
def identify(cls, user_id: int):
return cls.query.get(user_id)
@property
def identity(self):
return self.user_id
def is_valid(self):
return self.is_active
def __repr__(self):
return f"<User {self.user_id} - {self.username}>"
Что делает SqlAlchemy настолько мощным, так это то, что вы можете определить модель базы данных прямо здесь, в коде Python. Вы можете определить имя таблицы, свойства и даже другие собственные элементы SQL, такие как значения по умолчанию и уникальность. Это позволяет вам легко взаимодействовать с моделями и объектами базы данных в Python. Если вы не определяете метод здесь, в модели, вы все равно можете импортировать его и строить запросы в другом месте вашего кода.
Заполнение базы данных
Теперь, когда мы определили нашу строку подключения и определили нашу модель пользователя, давайте заполним нашу базу данных. К счастью, SQLAlchemy предоставляет мощные команды для поддержки базы данных. Эти команды скрыты за командами вызова. Чтобы инициализировать базу данных и заполнить ее, выполните две следующие команды.
invoke init-db
invoke seed-db
Эти две команды определены в tasks.py
файле и создают таблицы в нашей базе данных и заполняют их двумя пользователями.
Использование моделей SQLAlchemy для запроса базы данных
В файле chap-2/app/__init__.py
есть маршрут / uhoh, который определен там, где используется наша пользовательская модель.
Обратите внимание, как мы можем импортировать нашу модель User в начале нашей функции create_app
, а затем использовать ее в нашем маршруте для создания запроса, который выбирает всех пользователей. ПРИМЕЧАНИЕ. Если вы не знакомы с синтаксисом List [user], это синтаксис ввода Python с mypy.
Теперь, когда у нас есть определенный маршрут и запрашиваемая модель, давайте запустим GET-запрос, перейдя на http://127.0.0.1:5000/uhoh.
Вы должны увидеть ответ, в котором говорится: TypeError: объект типа user не является сериализуемым JSON.
Ошибка TypeError сообщает нам, что наш ответ модели SQLAlchemy не является сериализуемым в формате JSON. Другими словами, пользовательскую переменную нельзя так легко преобразовать в JSON. Вместо этого нам нужно десериализовать пользователя и преобразовать его в JSON. Сделать это можно с помощью пакета Marshmallow. Marshmallow - это базовый пакет при выполнении проверки ввода, десериализации в объекты уровня приложения и сериализации объектов уровня приложения в типы Python. Мы настоятельно рекомендуем вам ознакомиться с этим пакетом, поскольку он может предложить вам суперсилы для приложений-объектов, входящих и исходящих из вашего API.
Ниже приведен пример маршрута, который десериализует модель SQLAlchemy в JSON. Он использует определенную схему, чтобы знать, как десериализовать модель SQLAlchemy.
При вызове маршрута с помощью http://127.0.0.1:5000/marsh мы видим, что модели SQLAlchemy правильно преобразовываются в JSON, а веб-браузер отображает JSON.
Поздравляю! Теперь вы подключены к базе данных и у вас есть API, обслуживающий данные из нее!
Аутентификация и авторизация
С нашим API, подключенным к базе данных и обслуживающим данные из нее, давайте удостоверимся, что мы можем аутентифицировать пользователей, прежде чем использовать какие-либо конфиденциальные данные. Для этого воспользуемся Flask Praetorian. Flask Praetorian считается библиотекой JWT с батарейками. Другими словами, после установки этого пакета вы можете добавить аутентификацию и авторизацию с небольшими изменениями в свое приложение. Давайте рассмотрим несколько шагов, которые нам нужно сделать, чтобы добавить аутентификацию JWT.
В файлеchap-3/app/config.py
добавьте конфигурационные переменные JWT_ACCESS_LIFESPAN и JWT_REFRESH_LIFESPAN.
Затем в файле chap-3/app/__init__.py
мы можем инициализировать преторианскую библиотеку, добавить маршрут / login и защитить маршруты с помощью специального декоратора @auth_required.
"Main Flask App"
...
# Application Factory
# https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/
def create_app(config_name: str) -> Flask:
"""Create the Flask application
Args:
config_name (str): Config name mapping to Config Class
Returns:
[Flask]: Flask Application
"""
from app.config import config_by_name
from app.models import User
# Create the app
app = Flask(__name__)
# Log the current config name being used and setup app with the config
app.logger.debug(f"CONFIG NAME: {config_name}")
config = config_by_name[config_name]
app.config.from_object(config)
# Initialize the database
db.init_app(app)
# Initialize the flask-praetorian instance for the app
guard.init_app(app, User)
@app.route("/")
def hello_world() -> str: # pylint: disable=unused-variable
return "Hello World!"
@app.route("/login", methods=["POST"])
def login():
# Ignore the mimetype and always try to parse JSON.
req = request.get_json(force=True)
username = req.get("username", None)
password = req.get("password", None)
user = guard.authenticate(username, password)
ret = {"access_token": guard.encode_jwt_token(user)}
return (jsonify(ret), 200)
@app.route("/users", methods=["GET"])
@auth_required
def users():
users = User.query.all()
return jsonify(UserSchema(many=True).dump(users))
@app.route("/users/admin-only", methods=["GET"])
@roles_required("admin")
def users_admin():
users = User.query.all()
return jsonify(UserSchemaWithPassword(many=True).dump(users))
return app
Мы инициализируем защитную переменную с помощью нашей пользовательской модели SQLAlchemy. Это модель, которую Flask Praetorian будет использовать для аутентификации пользователей при выполнении guard.authenticate () в маршруте / login. Когда пользователь аутентифицирован, мы можем закодировать токен JWT от пользователя и отправить его обратно клиенту. Затем клиент может использовать этот токен при выполнении запросов к бэкэнду, например, в / users route. Декорированный @auth_required проверит заголовки запроса на наличие токена носителя аутентификации. Если токен присутствует, он проанализирует его и проверит пользователя в базе данных. Если пользователь аутентифицирован, маршрут может продолжаться. Создавая готовые к работе приложения, вы всегда должны учитывать, какие данные будут передаваться через приложение. При настройке приложений мы обрабатываем все данные как конфиденциальные, и пользователи должны пройти проверку подлинности. Это отличная передовая практика.
Flask Praetorian также дает нам авторизацию с помощью декоратора @roles_required, как показано в маршруте / users / admin-only. Этот декоратор аутентифицирует пользователя, а затем проверяет наличие у него правильных ролей, прежде чем разрешить продолжение запроса.
Чтобы самостоятельно увидеть эти действия, используйте файл с именем effortless_rest_with_flask_postman.json
, который позволяет вам быстро импортировать запросы Postman для работы с вашим локальным API.
Защита вашего приложения может быть невероятно сложной задачей, но Flask Praetorian упрощает ее. С помощью нескольких конфигураций конечных точек вы можете легко добавить аутентификацию и авторизацию. Теперь у вас есть API, подключенный к базе данных, а также маршруты с общей аутентификацией и дополнительной авторизацией. Было бы вполне разумно начать встраивать бизнес-логику в свое приложение прямо сейчас, но нам все еще чего-то не хватает. Как наши потребители API узнают о наших интерфейсах и входах API? На какую документацию они могут положиться, чтобы соответствовать нашему API? В части 3 этой серии мы ответим на эти вопросы с помощью автоматически созданной документации по API в нашем приложении Flask. До скорого!