Статья Авторизация и аутентификация web-приложений (for example python Flask)

Доброго времени суток codeby !


План :


Запланировал разобрать аутентификацию и авторизацию веб-приложений на примере Flask. Это первая часть из моей серии, т.к информации достаточно много и я стараюсь правильно ее распределить. Я сам если честно не люблю тонны скучных определений, поэтому постараюсь донести свои мысли более-менее интересно (не факт) Но все же теория - основа доказательства (только что придумал) и блин, я не понимаю откуда мое непреодолимое желание ставить скобки )) (ам сори)

Итак, если кратко, то в этой части хочу разобрать что вообще такое Фласк (если кто не знаком), для чего используются печеньки и сессии в веб-приложениях, запустить свое приложение для тестов и примеров.
Ну ок че ? Погнали)


О Flask :

Flask — микро-фреймворк для создания (Web Server Gateway Interface) приложений на языке Python. Точнее сказать можно, но думаю не нужно. Собственно, что означает микро и основная документация расположена , пока же мы запустим минимальное приложение для старта :

Код:
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page'

if __name__ == '__main__':
    app.run()

Screenshot_2.png


Вот так просто вы можете поднять свой веб-сервер на localhost:5000. Если вы хотите поднять сервер на конкретном адресе, изменить порт или использовать отладку ( желательно ), вам следует переопределить метод запуска как :

Python:
app.run(host='ip', port='port', debug=True)


Маршруты :

Python:
@app.route("/", methods=["GET", "POST"])
def main():
    return render_template('index.html')

Маршруты URL создаются с помощью декоратора @app.route. Функция render_template возвращает html шаблон. Все шаблоны html должны находится в каталоге templates внутри приложения. В каталоге static находятся подкаталоги css img js

Я не использовал всю мощь шаблонов jinja2 в полной мере, а просто нарисовал html примерно вот так :

HTML:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>project_xxs</title>
</head>
<body>
    <div class="container">
        <div class="alert alert-danger col-md-3" role="alert">
        {% with messages = get_flashed_messages() %}
          {% if messages %}
            <ul class=flashes>
            {% for message in messages %}
              <li>{{ message }}</li>
            {% endfor %}
            </ul>
          {% endif %}
        {% endwith %}
        </div>
        <form class ="col-md-3 col-sm-6" action="/auth" method="POST">
            <img src="{{ url_for('static', filename='img/auth.png') }}" class="img">
            <div class="form-group">
                <label for="exampleInputEmail1" class="mt-1">username</label>
                <input name="username" type="text" class="form-control mt-2" id="exampleInputEmail1">
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1" class="mt-1">password</label>
                <input name="password" type="password" class="form-control mt-2" id="exampleInputPassword1">
                <button type="submit" class="btn btn-primary mt-3">auth</button>
            </div>
        </form>
    </div>
</body>
</html>

Таким образом структура приложения внутри папки project :

Screenshot_6.png



Авторизация :

Авторизация - это механизм предоставляющий пользователю (после аутентификации) доступ к тем или иным ресурсам. В своем приложении я буду использовать расширения Flask-Login для авторизации пользователя. Авторизация происходит с помощью функции login_user() но Flask-Login ожидает от нас модель пользователя, которую нам необходимо создать, и чтобы не прописывать методы вручную можно наследоваться от UserMixin в db_models.py

Python:
# -*- coding: utf-8 -*-
from flask_login import UserMixin

from project_x import db


class Users(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), unique=True, nullable=False)
    password = db.Column(db.String(128), nullable=False)

login_user(user) вызывается в после проверки логина и пароля в форме, создает объект пользователя и заносит данные о нем в сессию.
С помощью декоратора @login_manager.user_loader при каждом запросе клиента будет загружаться информация о пользователе.

Python:
@login_manager.user_loader
def load_user(user_id):
    return Users.query.get(user_id)

Декоратора @login_required ограничивает доступ к маршруту не авторизованных пользователей.

Python:
@app.route("/", methods=["GET", "POST"])
@login_required
def main():
    return render_template('index.html')

Главная страница возвращает заглушку.


Cookie и сессии :

Файлы куки - это небольшие фрагменты данных, которые хранятся на вашем компьютере веб-браузером, и их первоначальная цель - запоминать информацию о состоянии при просмотре различных веб-сайтов (страниц).

Сессии также используются для хранения данных пользователя, но в отличие от печенек (которые полностью хранятся на стороне клиента) при сессиях в куках может хранится только идентификатор сессии. Другими словами сессии - это частный случай cookies, сервер кодирует данные и передает получившееся значение клиенту, клиент при следующих запросах передает эти данные обратно в заголовке Set-Cookies : после чего сервер проводит их декодирование и валидацию. При конфигурации приложения во Flask указывается секретный ключ, по которому приложение проверяет криптографически, были ли изменены данные сессии. Это так называемые - клиентские сессии. Фласк использует по умолчанию именно их.

Существуют серверные сессии, которые хранятся на стороне сервера, а в запросе клиента передается идентификатор, по которому сессия может быть валидирована и получена из БД.

Я добавил серверные сессии с помощью flask_sessionstore.


СУБД :

Чтобы работать с БД существует модуль flask_sqlalchemy. В данный момент я использую MySQL. Конфигурация приложения flask_sessionstore и flask_sqlalchemy находится в __init__.py :

Python:
# -*- coding: utf8 -*-
import os

from flask_sqlalchemy import SQLAlchemy
from flask import Flask, session
from flask_sessionstore import Session


app = Flask(__name__)
app.config.from_object(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:1234@localhost/project_x?charset=utf8mb4'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = str(os.urandom(64).hex())

db = SQLAlchemy(app)

app.config['SESSION_TYPE'] = 'sqlalchemy'
app.config['SESSION_SQLALCHEMY'] = db
app.config['SESSION_SQLALCHEMY_TABLE'] = 'sessions'

Session(app)

from project_x import db_models, routes

#session.app.session_interface.db.create_all()
#db.create_all()

app.run()

В __init__ импортируются все модули из пакета а start.py находится каталогом выше в Project и содержит from project_x import app
Таким образом происходит запуск приложения.


Серверные сессии :

При создании приложения создаются модели БД.

Screenshot_10.png



При первом запросе происходит редирект на /auth и устанавливаются куки сессии :

Screenshot_9.png



Screenshot_8.png


Отправляем данные из формы для авторизации (предварительно конечно есть форма регистрации) и получаем токен :

Screenshot_11.png


В клиентской куки передается только ай ди сессии. Для токена устанавливается значение fresh=True, если данные в куки или БД побьются, свежесть=false.
Чекнем :

Screenshot_12.png


Извиняюсь за поехавшие таблицы.

Мы также можем обратиться к объекту сессии, чтобы провести проверку дополнительно. Если убить сессию из БД Flask все равно считает пользователя авторизованным (не знаю почему, видимо я что то упустил) поэтому проверю свежий ли токен :

Python:
if session['_fresh'] == False:
        return redirect(url_for('auth_user'))

Я использовал простой метод авторизации и во Flask есть возможность это улучшить используя WTForms. В частности достаточно просто реализуется защита от CSRF атак.

Далее планирую реализовать формы и JSON WEB TOKEN (без запросов к БД) авторизацию, рассмотреть другие протоколы.


Спасибо за внимание !
 
Последнее редактирование:
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!