Рубрики
Без рубрики

Часть серии Flask Часть 10: позволяя пользователям регистрироваться и входить в систему

Введение в последнем посте в серии, мы улучшили UI / UX, добавив верхнюю навигационную панель, я … Теги с Python, Flask, WebDev.

Введение

На последнем посту в серии мы улучшили UI/UX, добавив верхнюю навигационную панель, встроенную с помощью формы входа.

Теперь пришло время извлечь выгоду из этой недавней функции пользовательского интерфейса и фактически расширить наше приложение, чтобы позволить пользователям регистрироваться и войти в наше приложение.

Конечная цель этой функции

Когда пользователи смогут зарегистрироваться и входить в систему, мы сможем расширить некоторые представления в нашем приложении, что мы сделаем только для вошедших в систему пользователей, а именно:

  1. «Добавить рецепт» View, где пользователи могут добавить свой собственный личный рецепт на сайт.

  2. Просмотр «приборной панели», где пользователи могут видеть детали рецептов, которые они добавили, и обновляют или удаляют их через табличное представление.

Дополнительно, очевидно, только зарегистрирован только пользователями, сможет выйти из системы.

Эта функция будет изучать множество дополнительных функций Flask, больших интеграций с внешними API и как настроить шаблоны Jinja, чтобы показать информацию о текущем пользователе.

Подготовка регистрационной формы

Чтобы позволить пользователям регистрироваться и войти в нашу приложение, нам нужно будет создать представления на интернет-конце, где пользователи могут быть перенаправлены на, чтобы выполнить соответствующие действия. Затем нам нужно будет написать соответствующие конечные точки на сервере, так что:

  1. Для регистрации конечная точка:

    • Новый пользовательский реестр отклонен в случае, если пользователь существует;
    • После того, как форма считается действительной, создается новый пользователь и добавляется в базу данных, с учетными данными, введенными в форме и со специальным дополнительным атрибутом, Is_active статус, который мы установим на ложь по умолчанию;
    • После этого после завершения регистрации пользователь перенаправляется на домашнюю страницу, и в качестве всплывающего окна будет отображаться мигающее сообщение, сообщая пользователю, что электронное письмо было отправлено. Потом:
      • Пользователь получит электронное письмо, в котором он может подтвердить свой счет, а после подтверждения активного статуса будет установлен на true, и пользователь будет войти в систему.
  2. Для конечной точки активации:

    • Активация в случае нашего приложения будет просто означать установку активного состояния пользователя, который нажал на ссылку электронной почты, и мы будем использовать эту информацию, чтобы обновить шаблоны интерфейса с помощью в текущем пользователе.
  3. Для конечной точки входа:

    • Вход в систему не удается в случае неправильной или неполной учетных данных, насколько мы перенаправляем обратно на страницу входа в систему/главную страницу;
    • Если учетные данные совпадают, мы установим текущий пользователь к одному с соответствующими учетными данными и войти в него/ее в приложение.

Безопасность приложений: Всегда используйте HTTPS. и хеш ваша база данных паролей

Перед тем, как мы влежаем на весь код для этой новой функции, важно говорить о безопасности приложений и безопасности баз данных.

Использование https против http

Вы всегда должны защищать все свои сайты с https Даже если они не справляются с чувствительными связями. Помимо предоставления критической безопасности и целостности данных для ваших веб-сайтов, так и для личной информации ваших пользователей, https Это требование для многих новых функций браузера, особенно тех, которые требуются для прогрессивных веб-приложений. Итак, теперь он принимается как современный веб-стандарт, и по причинам:

  • https Помогает предотвратить подделки злоумышленников с коммуникациями между вашими веб-сайтами и браузерами ваших пользователей. Злоумышленники включают намеренно злонамеренные злоумышленники и законные, но навязчивые компании, такие как ISP или отели, которые вставляют рекламу на страницы.

  • https Предотвращает возможность злоумышленников пассивно прослушать связь между вашими веб-сайтами и вашими пользователями.

  • https является ключевым компонентом для рабочих процессов разрешений для новых функций и обновленных API для прогрессивных веб-приложений: сфотографируемую или записи аудио с GetUsermedia (), позволяя внесению опыта приложений с обслуживанием и т. Д. Многие более старые API также обновляются для требуния разрешения на выполнение, такие как API геолокации.

Для того, чтобы использовать https В Heroku нам нужно насильно настроить его через наш код сервера Flask. Идея состоит в том, чтобы выполнить функцию перед каждым запросом, который заставит постоянный перенаправление всех запросов (через HTTP-код 301), который не имеет HTTPS. В своем заголовке, после замены http с https. Колбовая аннотация @ app.before_request Используется, чтобы убедиться, что эта функция вызывается перед каждым запросом. Код выглядит следующим образом:

@app.before_request
def enforce_https_in_heroku():
    if request.headers.get('X-Forwarded-Proto') == 'http':
        url = request.url.replace('http://', 'https://', 1)
        code = 301
        return redirect(url, code=code)

Заголовки запросов проверятся на существование http . На каком случае заголовки заменяются, а перенаправление на оригинальный (но теперь защищенный) URL возникает с кодом 301. Поскольку эта функция вызывается перед каждым запросом, все наши коммуникации будут защищены HTTPS. . Мы только что сделали наше приложение немного более безопасным.

Пароли базы данных охватывания базы данных

Как видно выше, используя https Важно предотвратить подделки злоумышленников с коммуникациями между веб-сайтом и браузером пользователей. Тем не менее, что, если злоумышленник удерживает нашу базу данных любыми средствами? В этом сценарии использование https Не имеет отношения к целям защиты, потому что база данных сидит на заднем дне, поэтому это означает, что злоумышленники непосредственно получили все данные в базе данных. К сожалению, даже в наши дни, очень распространено для многих веб-сайтов и веб-приложений, которые нуждаются в базах данных, до Хранить пароли в открытом тексте Отказ

Что это значит, что, если бы я зарегистрировался на веб-сайте с учетными данными: пользователь: Bruno Password: Bruno123 Это будут точные значения, сохраненные в базе данных. Это огромный риск безопасности с точки зрения компромисса базы данных. Любой злоумышленник, который получил эти данные, может легко входить в систему как я, запросить пароль изменения или изменить мои учетные данные по желанию, не говоря уже о том, чтобы не упомянуть, получить доступ к конфиденциальной информации. Многие магазины сегодня все еще делают это, и это ужасная ошибка:

Ключ на вынос: никогда не храните пароли в базе данных в открытом тексте. Используйте хеширование во все времена.

Трюк для предотвращения этого типа атаки, состоит в том, чтобы использовать Двигинг или любая другая форма одностороннее шифрование Отказ С одним путем шифрования это означает, что очень легко зашифровать определенное значение, но очень сложно (прочитать: «невозможно»), чтобы расшифровать исходное значение из зашифрованного.

Подобно, вместо того, чтобы хранить наложенные пароли, мы можем их использовать и хранить значение хэшированного пароля в базе данных: Пользователь: Bruno Pass: make_hash (“bruno123”) = <какая-то случайная строка символов, представляющих хешированное значение> Итак, если любой злонамеренный пользователь получает в нашей базе данных, он не сможет использовать информацию, которую он извлекал для любых вредных целей. Это легко сделать, и это добавляет огромное количество безопасности в приложение. Для этого мы будем использовать следующие две функции:

def hash_password(password):
    """Hash a password for storing."""
    salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
    pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
                                  salt, 100000)
    pwdhash = binascii.hexlify(pwdhash)
    return (salt + pwdhash).decode('ascii')

Эта функция хэширует пароль с солью. Соль – это случайные данные, которые используются в качестве дополнительного входа к односторонним функциям, которые «Hashes» данные, пароль или пароль. Соли используются для защиты паролей в хранении. Затем мы вычисляем хеш для пароля и добавляем ее соль, и мы получаем наш хешированный пароль, полностью защищенный от злоумышленника.

Затем нам нужно сравнить сохраненный пароль с паролем, который пользователь предоставляет (например, с помощью формы входа. Здесь https вопросы) и для этого мы будем использовать эту функцию:

def verify_password(stored_password, provided_password):
    """Verify a stored password against one provided by user"""
    salt = stored_password[:64]
    stored_password = stored_password[64:]
    pwdhash = hashlib.pbkdf2_hmac('sha512',
                                  provided_password.encode('utf-8'),
                                  salt.encode('ascii'),
                                  100000)
    pwdhash = binascii.hexlify(pwdhash).decode('ascii')
    return pwdhash == stored_password

Здесь мы извлекаем значение соли, а также сохраненный пароль, мы используем соль для хеширования пароля, который введен введенный пользователем, а затем мы сравниваем hashed пароль, который пользователь введен с тем, который мы получили, и только если они совпадают Сделайте матч пароли. Таким образом, мы гарантировали нашу безопасность на уровне базы данных, а также во время трафика между клиентом и сервером. Теперь, когда мы справились с безопасностью, мы готовы двигаться вперед и, наконец, внедрить нашу регистрационную форму.

Как форма будет выглядеть

Вот как наша простая регистрационная форма будет выглядеть:

У нас есть три поля для пользователя: имя, электронная почта и пароль.

Электронная почта будет использоваться для целей аутентификации, поскольку: это уникально, и могут быть несколько человек с тем же именем, но разные электронные письма, и каждое электронное письмо будет уникальным для зарегистрированного пользователя. Итак, если мы увидим электронное письмо уже в базе данных, реестр будет отклонен.

Как всегда, мы строим наш шаблон Jinja:





{% extends "base_template.html" %}

{% block body %}
    
    

Create an account

{{ form.hidden_tag() }}

{{ form.name.label }}
{{ form.name(size=32) }}

{{ form.email.label }}
{{ form.email(size=32) }}

{{ form.password.label }}
{{ form.password(size=32) }}

{{ form.submit() }}

{% endblock %}

Шаблон прост и простой: мы расширяемся от нашего базового шаблона, который мы ввели в предыдущий рассрочку, и просто создайте форму с необходимыми полями, которые нам понадобится от пользователя, чтобы сделать регистрацию.

В представлении мы видим, что форма будет перенаправлять введенные данные для /userregistry конечная точка, где будет происходить регистрация. Это то, что мы увидим, как реализовать дальше.

Реализация конечной точки реестра пользователя

Теперь, когда у нас есть интерфейс на месте, мы можем сосредоточиться на реализации /userregistry конечная точка.

Вот код:

@db_session
@app.route('/userRegistry', methods=['GET', 'POST'])
def user_registry():
    form = UserRegistryForm()
    if form.validate_on_submit():
        if request.method == 'POST':
            email = request.form['email']
            password = hash_password(request.form['password'])
            name = request.form['name']
            exist = User.get(login=email)
            if exist:
                flash('The address %s is already in use, choose another one' % email)
                return redirect('/userRegistry')
            curr_user = User(login=email, username=name, password=password, is_active=False)
            commit()
            localhost_url = 'http://0.0.0.0:5000'
            message = Mail(
                from_email='',
                to_emails=To(email),
                subject='Confirm your account',
                html_content='

Hello,

to complete your registration click here .' ) try: sg = SendGridAPIClient(os.environ.get("SENDGRID_API_KEY")) response = sg.send(message) except Exception as e: print e.message return redirect('/') else: return render_template('registry_form.html', title='Register', form=form)

Здесь здесь много, так что давайте пойдем на это шаг за шагом.

Аннотации

У нас было @db_session Аннотация до стандартной аннотации маршрута для указания колбы, которую мы будем делать манипуляции базы данных в конечной точке. Это похоже на с db_session Контекст локальный в блоке, но это объявляет его для всей конечной точки, и мы можем использовать операции БД на воле.

Ниже этой аннотации у нас есть стандартная аннотация колба для объявления конечной точки, и мы будем поддерживать как пост, так и получить методы. Метод Get будет использоваться при рендеринге шаблона, для того, когда пользователь сначала обращается к представлению, никакой информации не размещается на сервере, поэтому мы используем стандарт Получить Метод для визуализации шаблона.

Объявление формы для обработки нашей информации

В линии Форма () Мы объявляем форму для обработки информации о пользователе для регистрации.

Userregistryform это класс Python , что выглядит так:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired


class UserRegistryForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Register')

Мы используем WTForms создать нашу форму регистрации пользователей. Это расширение для колбы, которая делает обработку формы с колбой очень легкой задачей.

В WTForms форма представлена стандартным классом Python. Формы являются основным контейнером WTForms. Формы представляют собой коллекцию полей, которые могут быть доступны в стиле словаря формы или стиль атрибута.

Поля делают большую часть тяжелой подъема. Каждое поле представляет собой тип данных, и поле обрабатывает ввод Field Form в том, что тип данных DataType. Например, InteGerfield и Stringfield представляют два разных типа данных. Поля содержат ряд полезных свойств, таких как метка, описание и список ошибок проверки, в дополнение к данным, которое содержится в поле.

Каждое поле имеет экземпляр виджета. Работа виджета оказывает HTML-представление этого поля. Экземпляры виджета могут быть указаны для каждого поля, но каждое поле имеет один по умолчанию, что имеет смысл. Некоторые поля просто удобства, например TextAreafield – это просто Stringfield, с видомжетом по умолчанию, являющимся текстом.

Для того, чтобы указать правила проверки, поля содержат список валидаторов. Для нашего примера мы просто примечаем, что поля должны быть заполнены.

Чтобы на самом деле использовать форму, мы можем просто создать ее, и, благодаря тому, как работает, связывание данных с формой, мы можем получить доступ к ней через объект запроса и атрибутом формы, который является просто словарь, содержащим всю информацию, введенную в форма:

Email.form ['Email']

Таким образом, мы извлекаем значение электронной почты из формы.

Это имеет соответствующее значение на шаблоне Jinja:

{{ form.email.label }}
{{ form.email(size=32) }}

Это имеет метку и фактическое значение, сделанное полем электронной почты.

Обработка оформления пользователя

Следующим шагом является обработка регистрации пользователя. Для этого нам нужен класс пользователя, который мы можем разработать себя:

class User(db.Entity, UserMixin):
    login = Required(str, unique=True)
    username = Required(str)
    password = Required(str)
    is_active = Required(bool)
    recipes = Set("UserCreatedRecipe")

Как мы видим, у нас есть поле входа в систему, которое будет электронным письмом пользователя, так как уникально, у нас есть стандартные поля, имя пользователя и пароль, Is_active Статус и поле рецептов (которые мы будем углубиться в позже).

На данный момент самая важная особенность нашего Пользователь Класс – это тот факт, что он наследует от Ушмиксин сорт.

В Python, вот как вы используете наследство класса: вы объявляете супер класс как атрибут вашего подкласса напрямую. Тогда наше Пользователь Класс имеет доступ ко всем методам и свойствам Ушмиксин сорт.

Этот класс является специальным классом от Flask-login Расширение, которое содержит некоторые методы, которые должны иметь наш класс пользователей, поэтому, наследуя от него, мы получаем доступ к множеству функциональности, которые нам понадобится, чтобы управлять нашими пользователями.

С помощью этого класса определяется, теперь мы можем добавить логику для фактического обращения:

  • Проверка, существует ли пользователь;
  • Добавление нового пользователя в базу данных;

Код для этого есть:

exist = User.get(login=email)
            if exist:
                flash('The address %s is already in use, choose another one' % email)
                return redirect('/userRegistry')
            curr_user = User(login=email, username=name, password=password, is_active=False)
            commit()

Первая строка использует ORM PONY для попытки поиска пользователя ID (который является электронным письмом в нашем случае). Если мы успешным, это означает, что пользователь, зарегистрированный в этом письме, уже существует, насколько мы прошедшим сообщением к конечному пользователю, которое показывает эту информацию.

Быстрое введение в флэш-сообщения с колбой

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

Чтобы включить эти мигающие сообщения в интерфейсе UI, нам нужно внести изменения в наш шаблон Jinja:

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} {% endfor %} {% endif %} {% endwith %}

Type ingredients

Встроенный метод колбы, get_flashed_messages Работает, потянув все вспыхиваемые сообщения с сеанса и возвращая их. Дальнейшие звонки в том же запросе на функцию вернут те же сообщения. По умолчанию только сообщения возвращаются, но когда С_Категории установлен на Истинный , возвращаемое значение будет список кортежей в форме (категория, сообщение) вместо.

Фильтруйте вспышенные сообщения в одну или несколько категорий, предоставляя тем Категории в Catework_Filter Отказ Это позволяет рендеризировать категории в Отдельные блоки HTML. С_Категории и Catework_Filter Аргументы отличаются:

  • С_Категории Управляет, возвращаются ли категории с текстом сообщения ( True дает кортеж, где false дает только текст сообщения).
  • Catework_Filter Фильтрует сообщения вплоть до тех, кто соответствует предоставленным категориям.

Обработка регистрации пользователя (продолжение.)

После быстрого введения, как работать с мигающими сообщениями, мы можем приступить к нашему анализу кода.

Итак, после того, как мы добавим мигающее сообщение, мы перенаправляем пользователя обратно к тому же /userregistry Конечная точка, которая выполнит новый запрос GET, который приведет к очищенной форме, чтобы пользователь мог зарегистрироваться с правильным/новым адресом электронной почты.

Как только регистрация будет успешной, мы пишем новый пользователь в базу данных:

curr_user = User(login=email, username=name, password=password, is_active=False)
commit()

Обратите внимание, как мы устанавливаем Is_active Флаг к ложе. Это указывает, что пользователь еще не подтвердил свою регистрацию, нажав на ссылку, которая будет отправлена на электронную почту, использованную для регистрации. До тех пор, пока Is_active Флаг устанавливается на true, пользователь не сможет войти в систему, и будет отображаться мигающее сообщение, указывающее, что учетная запись еще не подтверждена.

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

Интеграция с API SendGrid – отправка электронных писем из нашего приложения

Есть много API для работы с транзакционный адрес электронной почты, MailGun и Sendgrid – это два из самых популярных из них, и их планы свободных уровней вполне подходят для обоих разработок, а также развертывание мелких веб-приложений.

Для того, чтобы интегрировать с любыми API, мы должны начать считать их документацию. Обычно провайдеры API предложат несколько способов отправки электронных писем: вы можете использовать CURL напрямую, чтобы отправить его/тестировать его из командной строки, и, в зависимости от языка программирования или окружающей среды, будут следующие инструкции для интеграции его с вашим Существующий код приложения.

После создания учетной записи с SENDGRID мы можем перейти к их руководству настроек под нашему имени пользователя в качестве раскрывающегося списка, и мы увидим это:

Итак, если мы следуем инструкциям для Python, мы находим этот фрагмент:

# using SendGrid's Python Library
# https://github.com/sendgrid/sendgrid-python
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail

message = Mail(
    from_email='from_email@example.com',
    to_emails='to@example.com',
    subject='Sending with Twilio SendGrid is Fun',
    html_content='and easy to do anywhere, even with Python')
try:
    sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    response = sg.send(message)
    print(response.status_code)
    print(response.body)
    print(response.headers)
except Exception as e:
    print(e.message)

Итак, добавив этот пример фрагмента, мы можем добавить функциональные возможности отправки электронных писем нашим пользователям через Python для API SENDGRID. ( Пожалуйста, обратите внимание, что API может и произойдет изменения со временем, поэтому синтаксис может отличаться. )

Примечательно, что здесь использование Os.environ.get ('sendgrid_api_key') Это инкапсулирует ключ API таким образом, который ссылается на систему, где выполняется код. Таким образом, ваши полномочия безопасно хранятся и не могут видеть, кто-нибудь, читая код.

Подтверждение учетных записей пользователей и вход в систему пользователя

После того, как пользователь получает и нажимает на ссылку в электронном письме, идея состоит в том, чтобы перенаправить поток кода к новой конечной точке, где пользователь будет войти в систему и перенаправлен на главную страницу:

@app.route('/activate/', methods=['GET', 'POST'])
def activate(id):
    with db_session:
        user = User.get(id=id)
        if user:
            user.is_active = True
            commit()
            print "Logged in"
            login_user(user)
            return redirect('/')

Мы просто извлекаем пользователя по ID, и, если оно существует, мы устанавливаем его статус активным и войти в систему.

Как только пользователь вошел в систему, пользовательский интерфейс на интерфейсе будет выглядеть следующим образом:

Итак, мы приветствуем зарегистрированные пользователю пользователем, используя его имя, поэтому то же имя, которое использовалось в регистрационной форме, поскольку он более удобный для отображения имени вместо электронной почты.

Давайте проверим код шаблона Jinja, чтобы увидеть, что необходимо изменить, чтобы добавить эту функцию в пользовательский интерфейс:

Home Register {% if not (current_user.is_authenticated and current_user.is_active)%} {% else %} {% endif %}

Итак, мы видим, что мы ссылаемся на текущий пользователь через teake_user Объект из Flask-login, который возвращает нас пользователя для текущего сеанса. Мы делаем несколько валидаций для проверки процесса входа в систему:

  • Мы видим, если teake_user аутентифицирован и одновременно, мы проверяем действительность Is_active Статус и, только если эти два условия действительны, мы настроим шаблон Jinja для отображения имени авторизованного пользователя;
  • Если один из этих условий недействителен, мы продолжаем показывать страницу входа;

Эти проверки будут происходить на другой конечной точке: конечная точка для регистрации пользователя в. В настоящее время мы вошли в систему напрямую после нажатия на ссылку в электронном письме, но, потом, на нормальном корпусе использования приложений, нам нужно будет войти, набрав в наших учетных данных. Этот поток и его валидация состоится в Вход Конечная точка, которую мы проанализируем сейчас.

Войдите через конечную точку входа в систему

При использовании приложения в обычном сценарии использования пользователю придется войти в систему через форму входа в систему, доступную на панели TOPNAV. Чтобы позволить этому, нам нужно представить содержимое небольшого компонента входа в систему, через запрос на почту на сервер, где будет обрабатываться логин:

Чтобы повторить, вот форма на стороне Jinja:


Итак, при отправке мы отправляем содержимое формы к Вход Конечная точка, для которой у нас есть следующий код:

@db_session
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        email = request.form['email']
        password = request.form['password']
        if email == '' or password == '':
            flash("Enter a name and password")
            return redirect('/')
        possible_user = User.get(login=email)
        if not possible_user:
            flash('Wrong username')
            return redirect('/')
        if verify_password(possible_user.password, password) and possible_user.is_active is True:
            print "Logged in"
            set_current_user(possible_user)
            login_user(possible_user)
            return redirect('/')
        flash('Wrong password or account not confirmed')
        return redirect('/')
    else:
        return render_template('base_template.html')

Здесь мы проверяем, чтобы убедиться, что оба поля заполнены, чтобы продолжить действие в логине, после чего мы следим за тем же потоком, который был продемонстрирован ранее:

  • Мы пытаемся получить пользователь по введенной электронной почте в форме входа в систему, не удалось, если мы не можем найти его, прошедшими сообщение об ошибке и перенаправляя на домашнюю страницу;

  • Если пользователь существует, мы выполняем проверку учетных данных через Verify_Password и Is_active Флаг, регистрация пользователя в случае успеха, или, снова перенаправляя и перепрошив сообщение об ошибке по ошибке;

Заключение

Это завершает реализацию функции регистрации и аутентификации пользователей в нашей заявке. Это охватывало много новых концепций, представило нас к новым расширениям колба, а также показали нам, как некоторые аспекты Python работают более подробно, такие как класс наследства. Это позволит нам создать оставшиеся функции приложения: панель инструментов, где пользователь может видеть свои собственные дополнительные рецепты в табличном представлении, а также создают компоненты для добавления новых рецептов в приложение.

Оригинал: “https://dev.to/brunooliveira/flask-series-part-10-allowing-users-to-register-and-login-1enb”