Оглавление
Flask включает в себя компоненты для работы:
Flask не включает в себя, но может быть расширен для работы:
Что происходит в минимальном приложении
Правила маршрутизации во Flask
Что такое фреймворк?
Фреймворк — набор готовых компонентов, из которых мы, как из деталек конструктора лего, собираем свое приложение.
Flask называют микрофреймворком, в отличие от других фреймворков, в которых есть компоненты на любой случай, у Flask по умолчанию всё очень просто. Разницу между фреймворком и микрофреймворком можно представить как разницу между квартирой с отделкой и квартирой без отделки.
Из чего состоит Flask
Как вы знаете из курса, чтобы приложение заработало, кто-то должен слушать порты и получать HTTP-запросы от клиентов. За это отвечает веб-сервер, и свой сервер у Flask есть. От веб-сервера запрос передается на WSGI-сервер — промежуточный сервер, который уже написан на Python и соединяет веб-сервер и приложение. Такой сервер тоже есть в пакете Flask. Наконец дальше запрос передается Flask-приложению — это то приложение, которое мы будем писать c использованием объектов (классов и функций) Flask.
Flask включает в себя компоненты для работы:
с маршрутами и методами,
статическими файлами,
загрузкой файлов
шаблонами,
cookies и сессиями,
ошибками,
JSON-данными,
тестами,
промежуточным слоем WSGI.
Flask не включает в себя, но может быть расширен для работы:
Далее, чтобы обрабатывать запросы к разным адресам, нужно привязать к разным адресам и методам разные функции. Например, функция page_index() будет обрабатывать запросы к адресу /index, функция page_catalog() будет обрабатывать запросы к адресу /catalog и так далее! Такие адреса обычно называются маршрутами. Маршрут с указанием метода называется эндпоинтом, а сам процесс сопоставления адреса и функции (или классов) — маршрутизацией.
с базой данных,
инструментами обработки форм,
инструментами контроля доступа,
инструментами кеширования,
админкой,
инструментами отладки,
очередями.
Маршрут или роут
это URL, к которому можно обратиться разными HTTP-методами. Маршрут может иметь несколько эндпоинтов.
Эндпоинт
это обращение к маршруту отдельным HTTP-методом. Эндпоинт выполняет конкретную задачу, принимает параметры и возвращает данные. Функция или класс, которые непосредственно обрабатывают запрос, называются view или «представлением». Представление работает с параметрами запроса и возвращает нужный результат.
Представление или вьюшка
объект Python (функция или класс), который получает данные HTTP-запроса и возвращает данные для HTTP-ответа.
Для того чтобы запустить код на Python как сайт (или приложение), нужен WSGI (Web Server Gateway Interface). Flask прячет от нас всю сложность работы с WSGI, так что у нас получается такой интерфейс для смертных, чтобы мы могли работать с созданием сайтов без какой-то подкапотной сложности. Для этого Flask использует пакет Werkzeug (веркцойг), который берет на себя всю работу со сложной обработкой веб-сервера.
Минимальное приложение Flask
# Сперва импортируем Flask
from flask import Flask
# Затем создадим экземпляр этого Flask, назовем его app -
# это будет наше приложение
app = Flask(__name__)
# Что такое __name__ ?
# При запуске сценария значение переменной __name__ равно __main__
# Эта переменная помогает Flask разбрираться, где он находится
# и без нее он просто не заработает
# Теперь создадим функцию, которая будет что-то делать
# Например, page_index — это функция, которая будет возвращать 'Hello World!'
def page_index():
return "Hello World!"
# Теперь используем метод у приложения, который зарегистрирует маршрут
# Например, для главной страницы будет вызвана функция page_index
app.add_url_rule('/',view_func=page_index)
#Теперь стартуем сервер, чтобы обрабатывать запросы от пользователей
app.run()
Что происходит в минимальном приложении
Flask запустился (программа работает в бесконечном цикле) и слушает порты. Получив обращение, Flask смотрит URL запроса и, если для похожего адреса зарегистрирована функция, вызывает функцию, получает ее значение и возвращает клиенту. Функция обрабатывает запрос и возвращает ответ. Клиент получает ответ и показывает браузеру.
На самом деле, код выше можно переписать проще с помощью декораторов (что это такое мы расскажем уже в следующем уроке). Код ниже читается так: при обращении на маршрут / запрос обрабатывается функцией page_index.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def page_index():
return "Главная страничка"
app.run()
Маршутизация во Flask
Чтобы создать страничку, просто добавьте маршрут в декоратор — и готово. Начнем мы с главной страницы или корня сайта. Все адреса называются со слеша / (косой черты), поэтому главная страница сайта — /
from flask import Flask
app = Flask(__name__)
@app.route("/")
def page_index():
return "Главная страничка"
app.run()
app.run() – команда, которая запускает сервер. Если мы используем ее, нам не нужно запускать Flask из консоли, достаточно запустить просто .py файл! Запустите ваше приложение, затем перейдите по ссылке, которая появилась в консоли! По умолчанию Flask будет запущен на хосте localhost или 127.0.0.1 и порту 5000, но мы можем переопределить хост и порт так:
app.run(host='0.0.0.0', port=8000)
Правила маршрутизации во Flask
Маршрут всегда начинается с /. Если не ставить в конце /, будут обрабатываться только адреса без /. Все названия функций должны быть уникальными. Функция должна возвращать строку.
Данные из сегментов URL
В начале занятия мы говорили, что пользователи отправляют данные, а приложение их обрабатывает. Как вытащить из URL что-то полезное и обработать в Python? Один из вариантов: передача данных прямо в адресной строке. Наверняка вы встречали адреса типа
профиль/111
и рядом с ним
профиль/222
В этом примере это не разные маршруты, просто мы передаем ссылку на тот профиль, который хотим получить. Значит, нашей программе каким-то образом нужно взять и получить вот эти 111 и 222 (ведь мы не хотим на каждого пользователя, который у нас есть в системе, создавать отдельную вьюшку). Чтобы это сделать, нам нужно из адресной строки получить переменную, превратить ее в переменную, а потом обработать и вернуть профиль пользователя.
Для этого в route передаем в угловых скобках название переменной, в которую запишется значение этой части URL.
Такая запись означает, что обрабатываются все адреса, которые начинаются с /profile, а дальше следует еще один сегмент адреса и именно этот сегмент отправится в переменную uid. Кстати, в примере мы используем переменную uid, но может быть и любая другая переменная. Итак, чтобы у каждого пользователя была своя страница, нам нужно получать идентификатор пользователя из URL. Чтобы у каждого товара была своя страница, нам нужно получать идентификатор товара из URL. Например:
/users/42
/catalog/items/420
Преобразование типов
Поскольку переменные берутся из адресной строки, они передаются в виде строки. Это легко проверить. Напишем такой код:
@app.route('/users/<uid>')
def profile(uid):
print(uid)
print(type(uid))
return ""
Переменная действительно имеет строковый тип, но у Flask есть встроенный инструмент, чтобы «на лету» превратить ее в число:
Затем откроем страницу:
/users/7
Такой запуск выведет в консоли:
7
str
@app.route('/users/<int:uid>')
def profile(uid):
print(uid)
print(type(uid))
return ""
Аналогично можно использовать:
<int:value> Для преобразования в челые числа
<float:value> Для преобразования в числа с точкой
<bool:value> Для преобразования в булевы значения
Оглавление
Пошаговый алгоритм использования marshmallow
Как сериализовать модель в JSON?
Как сериализовать модель в dict?
Как десериализовать JSON в модель ?
HTTP-методы и коды ответов на примере /books GET /books — получение списка сущностей
POST /books — создание новой записи
GET /books/{id} — получение конкретной сущности по ID
GET /books/{id} — получение конкретной сущности по ID
PUT /books{id} — полное обновление (всех полей) сущности по ID3
DELETE /books/{id} — удаление сущности по ID
PATCH /books/{id} — частичное обновление (не всех полей) сущности по ID
Сопоставление поведения для элемента и коллекций
Глоссарий
Сериализация и десериализация данных — это преобразование между необработанной структурой данных и экземплярами классов для их хранения и передачи.Например, преобразование объектов Python в JSON-представление. Marshmallow — это библиотека, которая не зависит от ORM/ODM/фреймворка, для конвертации сложных структур данных в JSON и обратно. Схема — это описание полей сложной структуры.
REST — это не спецификация, не стандарт.
REST — это архитектурный стиль, парадигма, подход.
REST API — это способ организации API.
RESTful Service — это сервис, у которого API организован при помощи REST.
Marshmallow
Типы полей в схеме: https://marshmallow.readthedocs.io/en/stable/_modules/marshmallow/fields.html
- Str — строка.
- Int — число.
- Email — email.
- DateTime — дата-время.
- Bool — булево.
- Float — флоат, число с плавающей запятой.
- Decimal — десимл, число с фиксированной точностью, то есть определенным количеством знаков после запятой.
Пошаговый алгоритм использования marshmallow
# Импорт
from marshmallow import Schema, fields
# Это модель, в ней мы ничего не меняем
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
year = db.Column(db.Integer)
author = db.Column(db.String)
# Описать сложную модель в виде класса схемы
class BookSchema(Schema):
id = fields.Int(dump_only=True)
name = fields.Str()
year = fields.Int()
author = fields.Str()
book = Book(id=1, name="Kolobok", author="people")
# Создать экземпляр схемы
book_schema = BookSchema()
# Выполнить метод схемы
json_string = book_schema.dumps(book)
Как сериализовать модель в JSON?
book = Book(id=1, name="Kolobok", author="people")
# Создать экземпляр схемы
book_schema = BookSchema()
# Выполнить метод схемы
print(book_schema.dumps(book))
Как сериализовать модель в dict?
book = Book(id=1, name="Kolobok", author="people")
book_schema = BookSchema()
print(book_schema.dump(book))
Как десериализовать JSON в модель ?
book_json_str = '{"name": "Kolobok", "author": "people", "year": 1567}'
book_schema = BookSchema()
# Получили dict
book_dict = book_schema.loads(book_json_str)
# Превращаем dict в модель
book = Book(**book_dict)
Правила REST
1. Используйте множественное число в ресурсах. 2. Не используйте глаголы. 3. Используйте только строчные буквы(lowercase). 4. Старайтесь делать короткие URL.
HTTP-методы и коды ответов на примере /books GET /books — получение списка сущностей
*Получает:* ничего *Возвращает:* в body JSON массив запрашиваемых сущностей *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
POST /books — создание новой записи
*Получает:* в body: JSON с полями сущности **Заголовок:** Content-Type: application/json *Возвращает:* 201 код пустое body *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
GET /books/{id} — получение конкретной сущности по ID
*Получает:* идентификатор сущности в query path (URL) *Возвращает:* в body JSON с полями искомой сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
GET /books/{id} — получение конкретной сущности по ID
*Получает:* идентификатор сущности в query path (URL) *Возвращает:* в body JSON с полями искомой сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
PUT /books{id} — полное обновление (всех полей) сущности по ID3
*Получает:* идентификатор сущности в query path (URL) в body JSON с всеми полями сущности *Возвращает:* 204 код в body ничего или все поля сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
DELETE /books/{id} — удаление сущности по ID
*Получает:* идентификатор сущности в query path (URL) *Возвращает:* 204 код в body ничего *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
PATCH /books/{id} — частичное обновление (не всех полей) сущности по ID
*Получает:* идентификатор сущности в query path (URL) в body JSON с полями сущности, которые надо обновить *Возвращает:* 204 код в body ничего или все поля сущности *Если ошибка:* 4xx или 5xx ошибку в body может быть сообщение и/или код ошибки
Сопоставление поведения для элемента и коллекций
https://www.notion.so/69d0ea43d35745c19f7fb1c37668872c?v=fcdb9cbfdb32436f940cac6e56b78757
HTTP Status Codes
200: OK
201: Created
204: No Content
300: Multiple Choices
301: Moved Permanently
400: Bad Request
401: Unauthorized
403: Forbidden
404: Not Found
405: Method Not Allowed
500: Internal Server Error
503: Service Unavailable
Оглавление
Как сконфигурировать Flask-RESTX
Как сконфигурировать неймспейс в Flask-RESTX
Как выглядит Class-Based View со всеми методами и сериализацией?
Глоссарий
**Class Based View** — это способ оформления хендлеров (@), views (представления, вьюх) и endpoints в виде класса с методами get/post/put/patch/delete. **Flask-RESTX** — это фреймворк для упрощения создания RESTful-сервиса с использованием Flask. Он дает возможность создавать Class-Based View и агрегировать эндпоинты в неймспейсы.
Как сконфигурировать Flask-RESTX
from flask import Flask
from flask_restx import Api, Resource
app = Flask(__name__)
api = Api(app)
Как сконфигурировать неймспейс в Flask-RESTX
from flask import Flask
from flask_restx import Api, Resource
app = Flask(__name__)
api = Api(app)
notes_ns = api.namespace('notes')
# регистрируем вот так класс, и тогда все методы
# этого класса будут доступны по пути /notes
@notes_ns.route('/')
class NotesView(Resource):
...
# регистрируем вот так класс, и тогда все методы
# этого класса будут доступны по пути /notes/<id>
@notes_ns.route('/<int:nid>')
class NoteView(Resource):
...
# --------------------------------------------------------------------------------
# а если создать неймспейс, вот так
all_ns = api.namespace('')
# а классы регистрировать вот так
@all_ns.route('/notes')
class NotesView(Resource):
...
@all_ns.route('/notes/<int:nid>')
class NoteView(Resource):
...
# то ничего не изменится, но стало менее удобно
Как выглядит Class-Based View со всеми методами и сериализацией?
from flask import Flask
from flask_restx import Api, Resource
app = Flask(__name__)
api = Api(app)
book_ns = api.namespace('books')
# здесь регистрируем класс (CBV) (ресурс) по определенному пути (эндпоинту)
@book_ns.route('/')
# наследуем класс от класса Resource
class BooksView(Resource):
# пишем все нужные методы
# получение списка сущностей, мы отдаем список всех сущностей из БД
def get(self):
all_books = Book.query.all()
return books_schema.dump(all_books), 200
# создание сущности, здесь мы получаем данные из запроса и создаем новую сущность в БД
def post(self):
req_json = request.json
new_user = Book(**req_json)
with db.session.begin():
db.session.add(new_user)
return "", 201
@book_ns.route('/<int:uid>')
class BookView(Resource):
# получение конкретной сущности по идентификатору
def get(self, uid: int):
try:
book = Book.query.get(uid)
return book_schema.dump(book), 200
except Exception as e:
return "", 404
# обновление сущности по идентификатору
def put(self, uid):
book = Book.query.get(uid)
req_json = request.json
book.name = req_json.get("name")
book.email = req_json.get("email")
book.age = req_json.get("age")
db.session.add(book)
db.session.commit()
return "", 204
# частичное обновление сущности, здесь мы получаем только часть полей и их обновляем у сущности
def patch(self, uid):
book = Book.query.get(uid)
req_json = request.json
if "name" in req_json:
book.name = req_json.get("name")
if "email" in req_json:
book.email = req_json.get("email")
if "age" in req_json:
book.age = req_json.get("age")
db.session.add(book)
db.session.commit()
return "", 204
# удаление сущности
def delete(self, uid: int):
user = Book.query.get(uid)
db.session.delete(user)
db.session.commit()
return "", 204
Оглавление
Архитектура
это каркас приложения, структура директорий и способы взаимодействия объектов друг с другом.
1. Производительность — это то, насколько быстро ваше приложение обрабатывает запросы. 2. Расширяемость — это то, насколько легко и красиво можно добавить новую функцию в приложение в будущем. 3. Тестируемость — это то, насколько легко и качественно можно протестировать ваше приложение. 4. Поддерживаемость — это то, насколько легко и быстро можно будет исправить ошибки и/или неточности, найденные в приложении.. 5. Читаемость — легко ли читать код, понимать, что он делает, и осознавать логику, которую в этот код заложил разработчик.
Проект с готовой упрощенной архитектурой, в которой: 1. Вынесены Resources в views/<entity_name>.py. 2. Вынесены models в models.py. 3. Настройка БД в setup_db.py. 4. Создан config. https://github.com/skypro-008/lesson18/tree/main/project-easy
Простая архиектура
Q: Где находятся views? A: В views/entity_name.py.
Q: Где находятся модели и схемы сериализации? A: В models.py.
Q: Где настройка Flask-SQLAlchemy? A: В setup_db.py.
Q: В какой функции в app.py мы работаем с расширениями Flask (Flask-RESTX, Flask-SQLAlchemy)? A: В register_extenstions.
Оглавление
Архитектура MVC в нашей реализации
Глоссарий
**Бизнес-логика** — это логика, алгоритмы, код, связанный с доменными сущностями (относящиеся к предметной области), а не к обвязке вокруг нее. **DAO** (Data Access Object) — объекты с доступом к данным в хранилище. **Предметная область** — это объекты, которые относятся к предметной области, например классы «Пользователь», «Книга», «Фильм».
Простая архитектура
Архитектура MVC в нашей реализации
Селф-чек
Q: Где находятся модели? A: В dao/models/entity_name.py.
Q: Где находится DAO? A: В dao/entity_name.py.
Q: Где находится бизнес-логика? A: В service/entity_name.py.
Q: Кто использует DAO? A: Сервисы.
Q: Кто использует сервисы? A: views
Q: Как идет dataflow данных? A: views → service → dao → storage
Оглавление
Как выглядит простой декоратор
Как работать с JWT при помощи PyJWT
Какие виды аутентификации есть
Как применить декоратор на метод CBV
JWT — JSON Web Token
Аутентификация — процедура проверки подлинности, например проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.
Идентификация — процедура, в результате которой для субъекта идентификации выявляется его идентификатор. Идентификатор — это логин и пароль. Например, когда пользователь логинится, он вводит свои логин и пароль.
Авторизация — предоставление пользователю прав на выполнение определенных действий.
Декоратор — функция, которая позволяет обернуть другую функцию для расширения ее функциональности без непосредственного изменения ее кода.
Декораторы
# Создаем декоратор
def decorator_name(func):
def wrapper():
print('До выполнения функции')
func()
print('После выполнения функции')
return wrapper
# Применяем декоратор
@decorator_name
def my_func():
print('OK')
# Смотрим результат
$ python3 app.py
До выполнения функции
OK
После выполнения функции
В чем смысл декоратора
В расширении функциональности без изменения кода функции.
Как выглядит простой декоратор
def decorator_name(func):
def wrapper():
print('До выполнения функции')
func()
print('После выполнения функции')
return wrapper
JWT
# Устанавливаем библиотеку для работы с JWT. pip3 install pyjwt
# Какая библиотека используется в Python для работы с JWT? PyJWT
# Как выглядит декоратор проверки JWT в заголовке Authorization реквеста?
def auth_required(func):
def wrapper(*args, **kwargs):
if 'Authorization' not in request.headers:
abort(401)
data = request.headers['Authorization']
token = data.split("Bearer ")[-1]
try:
jwt.decode(token, secret, algorithms=[algo])
except Exception as e:
print("JWT Decode Exception", e)
abort(401)
return func(*args, **kwargs)
return wrapper
Как работать с JWT при помощи PyJWT
import calendar
import datetime
import jwt
secret = 's3cR$eT'
algo = 'HS256'
# Создаем токен с временем истечения
def generate_token(data):
min30 = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
data["exp"] = calendar.timegm(min30.timetuple())
return jwt.encode(data, secret, algorithm=algo)
# Проверяем валидность токена и не истек ли он
def check_token(token):
try:
jwt.decode(token, secret, algorithms=[algo])
return True
except Exception as e:
return False
if __name__ == '__main__':
data = {
'username': 'myname',
'role': 'user'
}
token = generate_token(data)
is_ok = check_token(token)
Какие виды аутентификации есть
- Basic
- Token
- OAuth 2.0
Что такое JWT
Это JSON-объект, который определен в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. header.payload.signature
Как сгенерировать токен
data = {
"username:" "ok",
"role": "user"
}
secret = "Secret"
algo = 'HS256'
token = jwt.encode(data, secret, algorithm=algo)
Как применить декоратор на метод CBV
import jwt
from flask import request, abort, Flask
from flask_restx import Api, Resource
algo = 'HS256'
secret = 's3cR3t'
def auth_required(func):
def wrapper(*args, **kwargs):
if 'Authorization' not in request.headers:
abort(401)
data = request.headers['Authorization']
token = data.split("Bearer ")[-1]
try:
jwt.decode(token, secret, algorithms=[algo])
except Exception as e:
print("JWT Decode Exception", e)
abort(401)
return func(*args, **kwargs)
return wrapper
app = Flask(__name__)
api = Api(app)
book_ns = api.namespace('')
@book_ns.route('/books')
class BooksView(Resource):
def get(self):
return [], 200
@auth_required
def post(self):
return "", 201
if __name__ == '__main__':
app.run(debug=False)
Оглавление
### Как злоумышленник может получить пароль?
Как проверить, что хеши одинаковые?
Как проверить валидность refresh-токена?
Глоссарий
Хеш-функция — это функция, которая необратимо искажает исходную строку (пароль), получается хеш, так что нельзя получить из хеша исходную строку — пароль.
Соль — строка, которая добавляется перед паролем, чтобы хеш получился сложнее и его сложнее было подобрать.
Хеш — это искаженная строка (результат работы хеш-функции), построенная на базе другой, нормальной строки — пароля.
Регистрация
# views/users
from flask import request
from flask_restx import Resource, Namespace
from marshmallow import Schema, fields
from helpers import auth_required
from implemented import user_service
user_ns = Namespace('users')
class UserSchema(Schema):
id = fields.Int()
username = fields.Str()
password = fields.Str()
role = fields.Str()
@user_ns.route('/')
class UsersView(Resource):
def get(self):
all_users = user_service.get_all()
res = UserSchema(many=True).dump(all_users)
return res, 200
def post(self):
req_json = request.json
user = user_service.create(req_json)
return "", 201, {"location": f"/users/{user.id}"}
@user_ns.route('/<int:uid>')
class UserView(Resource):
@auth_required
def delete(self, uid):
user_service.delete(uid)
return "", 204
# service/user.py
import base64
import hashlib
import hmac
from constants import PWD_HASH_ITERATIONS, PWD_HASH_SALT
from dao.user import UserDAO
class UserService:
def __init__(self, dao: UserDAO):
self.dao = dao
def get_one(self, bid):
return self.dao.get_one(bid)
def get_all(self):
return self.dao.get_all()
def get_by_username(self, username):
return self.dao.get_by_username(username)
def create(self, user_d):
user_d["password"] = self.make_user_password_hash(user_d.get("password"))
return self.dao.create(user_d)
def update(self, user_d):
user_d["password"] = self.make_user_password_hash(user_d.get("password"))
self.dao.update(user_d)
return self.dao
def delete(self, rid):
self.dao.delete(rid)
def make_user_password_hash(self, password):
return base64.b64encode(hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
PWD_HASH_SALT,
PWD_HASH_ITERATIONS
))
def compare_passwords(self, password_hash, other_password) -> bool:
return hmac.compare_digest(
base64.b64decode(password_hash),
hashlib.pbkdf2_hmac('sha256', other_password.encode(), PWD_HASH_SALT, PWD_HASH_ITERATIONS)
)
# dao/model/user.py
from setup_db import db
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
password = db.Column(db.String)
role = db.Column(db.String)
# dao/user.py
from dao.model.user import User
class UserDAO:
def __init__(self, session):
self.session = session
def get_one(self, bid):
return self.session.query(User).get(bid)
def get_by_username(self, username):
return self.session.query(User).filter(User.username == username).first()
def get_all(self):
return self.session.query(User).all()
def create(self, user_d):
ent = User(**user_d)
self.session.add(ent)
self.session.commit()
return ent
def delete(self, rid):
user = self.get_one(rid)
self.session.delete(user)
self.session.commit()
def update(self, user_d):
user = self.get_one(user_d.get("id"))
user.name = user_d.get("name")
user.password = user_d.get("password")
self.session.add(user)
self.session.commit()
Пароль
### Какой пароль считается сложным?
Тот, который соответствует всем требованиям:
- Минимум 8 символов (UhS^4b2FSz).
- Минимум 1 буква в верхнем регистре (Z).
- Минимум 1 цифра (3).
- Минимум 1 спецсимвол (%).
- В нем не должно быть словарного слова (password, magic, baby, etc.).
- В нем не должно быть закодировано слово (MyB@BY или r@@m).
### Как злоумышленник может получить пароль?
- Брутфорс.
- Похищение базы с хешами.
- Социальная инженерия.
Хеши
Как захешировать пароль солью?
base64.b64encode(hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
'<anything>',
1000
))
Как проверить, что хеши одинаковые?
hmac.compare_digest(
base64.b64decode(password_hash),
hashlib.pbkdf2_hmac('sha256', other_password.encode(), '<anything>', 1000)
)
refresh_token
### **Зачем нужен refresh_token?** Чтобы не заставлять пользователя вводить свои данные для получения access_token.
Как сгенерить refresh_token?
Так же, как access_token.
days130 = datetime.datetime.utcnow() + datetime.timedelta(days=130)
data["exp"] = calendar.timegm(days130.timetuple())
refresh_token = jwt.encode(data, JWT_SECRET, algorithm=JWT_ALGORITHM)
Как проверить валидность refresh-токена?
Так же, как и access-токена.
secret = 's3cR$eT'
algo = 'HS256'
# Если метод decode не выдаст exception — токен валиден
def check_token(token):
try:
jwt.decode(token, secret, algorithms=[algo])
return True
except Exception as e:
return False
Проверка роли
Пример декоратора, проверяющего роль:
secret = 's3cR$eT'
algo = 'HS256'
def admin_required(func):
def wrapper(*args, **kwargs):
if 'Authorization' not in request.headers:
abort(401)
data = request.headers['Authorization']
token = data.split("Bearer ")[-1]
try:
user = jwt.decode(token, secret, algorithms=[algo])
role = user.get("role")
if role != "admin":
abort(400)
except Exception as e:
print("JWT Decode Exception", e)
abort(401)
return func(*args, **kwargs)
return wrapper
Про передачу параметра роли в декораторе читайте в статье «Python: декорируем декораторы» https://habr.com/ru/post/187482/