Предоставление пользователям возможности ввода учетных данных для входа — одна из самых распространенных возможностей, добавляемых в веб-приложения. В этой статье мы расскажем, как добавить аутентификацию в ваше приложение Flask с помощью пакета Flask-Login.
Мы построим несколько образцов страниц регистрации и входа, которые позволят пользователям входить в приложение и видеть защищенные страницы, невидимые для пользователей, не выполнивших вход. Мы соберем информацию пользовательской модели и будем выводить ее на наших защищенных страницах при входе пользователя, чтобы смоделировать вид профиля.
В этой статье мы затронем следующие вопросы:
Исходный код этого проекта доступен на GitHub.
Для этого обучающего модуля вам потребуется следующее:
Наше приложение будет использовать шаблон фабрики приложений Flask с готовыми проектами. У нас будет один проект, отвечающий за все связанное с аутентификацией, и еще один проект для обычных маршрутов, включая указатель и страницу защищенного профиля. В реальном приложении функции можно распределять любым удобным образом, однако описанное здесь решение хорошо подходит для этого учебного модуля.
Здесь имеется схема, которая поможет понять, как будет выглядеть структура файлов проекта после завершения прохождения учебного модуля:
.
└── flask_auth_app
└── project
├── __init__.py # setup our app
├── auth.py # the auth routes for our app
├── db.sqlite # our database
├── main.py # the non-auth routes for our app
├── models.py # our user model
└── templates
├── base.html # contains common layout and links
├── index.html # show the home page
├── login.html # show the login form
├── profile.html # show the profile page
└── signup.html # show the signup form
По мере выполнения этого учебного модуля мы будем создавать эти каталоги и файлы.
Для нашего проекта нам потребуется три основных проекта:
Мы будем использовать SQLite, чтобы не устанавливать дополнительные зависимости для базы данных.
Для начала мы создадим каталог проекта:
- mkdir flask_auth_app
Затем нам нужно будет перейти в каталог проекта:
- cd flask_auth_app
Если у нас нет среды Python, нам нужно будет ее создать. В зависимости от способа установки Python на вашем компьютере команды будут выглядеть примерно так:
- python3 -m venv auth
- source auth/bin/activate
Примечание. Вы можете использовать учебный модуль по вашей локальной среде для получения информации по настройке venv
.
Выполните в своей виртуальной среде следующие команды для установки необходимых пакетов:
- pip install flask flask-sqlalchemy flask-login
Мы установили пакеты и теперь можем создать основной файл приложения.
Для начала мы создадим каталог проекта
:
- mkdir project
В первую очередь мы начнем работать над файлом __init__.py
для нашего проекта:
- nano project/__init__.py
Этот файл будет содержать функцию создания нашего приложения, которая инициализирует базу данных и зарегистрирует наши проекты. На данный момент мы не увидим изменений, но это потребуется для создания остальных частей приложения. Нам нужно будет инициализировать SQLAlchemy, настроить определенные значения конфигурации и зарегистрировать здесь наши проекты.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# init SQLAlchemy so we can use it later in our models
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret-key-goes-here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
db.init_app(app)
# blueprint for auth routes in our app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
Теперь у нас готов основной файл приложения, и мы можем начать добавление маршрутов.
Для наших маршрутов мы будем использовать два проекта. В основном проекте у нас будет главная страница (/
) и страница профиля (/profile
), открываемая после входа. Если пользователь попытается получить доступ к странице профиля без входа в систему, он будет направлен на маршрут входа.
В нашем проекте auth у нас будут маршруты для получения страницы входа (/login
) и страницы регистрации (/sign-up
). Также у нас имеются маршруты для обработки запросов POST от обоих этих маршрутов. Наконец, у нас имеется маршрут выхода (/logout
) для выхода активного пользователя из системы.
Пока что мы определим login
, signup
и logout
простыми возвратами. Мы вернемся к ним немного позднее и обновим их, добавив желаемые функции.
Вначале создайте файл main.py
для main_blueprint
:
- nano project/main.py
from flask import Blueprint
from . import db
main = Blueprint('main', __name__)
@main.route('/')
def index():
return 'Index'
@main.route('/profile')
def profile():
return 'Profile'
Затем создайте файл auth.py
для auth_blueprint
:
- nano project/auth.py
from flask import Blueprint
from . import db
auth = Blueprint('auth', __name__)
@auth.route('/login')
def login():
return 'Login'
@auth.route('/signup')
def signup():
return 'Signup'
@auth.route('/logout')
def logout():
return 'Logout'
В терминале вы можете задать значения FLASK_APP
и FLASK_DEBUG
:
- export FLASK_APP=project
- export FLASK_DEBUG=1
Переменная среды FLASK_APP
сообщает Flask, как загружать приложение. Она должна указывать на место создания create_app
. Для наших целей мы будем использовать указатели на каталог project
.
Переменная среды FLASK_DEBUG
активируется посредством присвоения ей значения 1
. Это активирует отладчик, который будет отображать в браузере ошибки приложения.
Убедитесь, что вы находитесь в каталоге flask_auth_app
и запустите проект:
- flask run
Теперь у вас должна появиться возможность открыть в браузере пять возможных URL-адресов и увидеть возвращаемый текст, определенный в файлах auth.py
и main.py
.
Например, если открыть адрес localhost:5000/profile
, появится: Profile
:
Мы проверили поведение наших маршрутов и можем перейти к созданию шаблонов.
Давайте продолжим и создадим шаблоны, которые используются в нашем приложении. Это будет первый шаг, прежде чем мы сможем реализовать реальную функцию входа. Наше приложение будет использовать четыре шаблона:
Также у нас будет базовый шаблон, который будет содержать общий код для каждой из страниц. В данном случае базовый шаблон будет содержать ссылки навигации и общий макет страницы. Давайте создадим их сейчас.
Для начала создайте каталог templates
в каталоге project
:
- mkdir -p project/templates
Затем создайте base.html
:
- nano project/templates/base.html
Добавьте следующий код в файл base.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flask Auth Example</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
</head>
<body>
<section class="hero is-primary is-fullheight">
<div class="hero-head">
<nav class="navbar">
<div class="container">
<div id="navbarMenuHeroA" class="navbar-menu">
<div class="navbar-end">
<a href="{{ url_for('main.index') }}" class="navbar-item">
Home
</a>
<a href="{{ url_for('main.profile') }}" class="navbar-item">
Profile
</a>
<a href="{{ url_for('auth.login') }}" class="navbar-item">
Login
</a>
<a href="{{ url_for('auth.signup') }}" class="navbar-item">
Sign Up
</a>
<a href="{{ url_for('auth.logout') }}" class="navbar-item">
Logout
</a>
</div>
</div>
</div>
</nav>
</div>
<div class="hero-body">
<div class="container has-text-centered">
{% block content %}
{% endblock %}
</div>
</div>
</section>
</body>
</html>
Этот код создаст серию ссылок меню на каждую страницу приложения, а также область, где будет отображаться контент.
Примечание. За кулисами мы используем Bulma для работы со стилями и макетом. Чтобы узнать больше о Bulma, ознакомьтесь с официальной документацией Bulma.
Затем создайте файл templates/index.html
:
- nano project/templates/index.html
Добавьте в созданный файл следующий код, чтобы заполнить страницу содержанием:
{% extends "base.html" %}
{% block content %}
<h1 class="title">
Flask Login Example
</h1>
<h2 class="subtitle">
Easy authentication and authorization in Flask.
</h2>
{% endblock %}
Этот код создаст базовую страницу указателя с заголовком и подзаголовком.
Затем создайте страницу templates/login.html
:
- nano project/templates/login.html
Этот код генерирует страницу входа с полями Email и Password. Также имеется поле для отметки запоминания сеанса входа.
{% extends "base.html" %}
{% block content %}
<div class="column is-4 is-offset-4">
<h3 class="title">Login</h3>
<div class="box">
<form method="POST" action="/login">
<div class="field">
<div class="control">
<input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-large" type="password" name="password" placeholder="Your Password">
</div>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox">
Remember me
</label>
</div>
<button class="button is-block is-info is-large is-fullwidth">Login</button>
</form>
</div>
</div>
{% endblock %}
Создайте шаблон templates/signup.html
:
- nano project/templates/signup.html
Добавьте в файл следующий код, чтобы создать страницу регистрации с полями адреса электронной почты, имени и пароля:
{% extends "base.html" %}
{% block content %}
<div class="column is-4 is-offset-4">
<h3 class="title">Sign Up</h3>
<div class="box">
<form method="POST" action="/signup">
<div class="field">
<div class="control">
<input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-large" type="password" name="password" placeholder="Password">
</div>
</div>
<button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
</form>
</div>
</div>
{% endblock %}
Затем создайте шаблон templates/profile.html
:
- nano project/templates/profile.html
Добавьте этот код, чтобы создать простую страницу с закодированным заголовком, welcome Anthony:
{% extends "base.html" %}
{% block content %}
<h1 class="title">
Welcome, Anthony!
</h1>
{% endblock %}
Позднее мы добавим код для динамического приветствия пользователя.
После добавления шаблонов мы можем обновить выражения возврата в каждом из маршрутов, чтобы вместо текста возвращались шаблоны.
Обновите файл main.py
, изменив строку импорта и маршруты index
и profile
:
from flask import Blueprint, render_template
...
@main.route('/')
def index():
return render_template('index.html')
@main.route('/profile')
def profile():
return render_template('profile.html')
Теперь мы обновим auth.py
, изменив строку импорта и маршруты login
и signup
:
from flask import Blueprint, render_template
...
@auth.route('/login')
def login():
return render_template('login.html')
@auth.route('/signup')
def signup():
return render_template('signup.html')
После внесения этих изменений страница регистрации будет следующим образом при переходе в /sign-up
:
Теперь вы должны видеть страницы /
, /login
и /profile
.
Пока что мы оставим /logout
отдельно, потому что эта страница не отображает шаблон.
Наша пользовательская модель отражает, что означает наличие пользователя для нашего приложения. У нас есть поля адреса эл. почты, пароля и имени. В приложении вы можете указать, хотите ли хранить больше информации для каждого пользователя. Вы можете добавлять такие вещи как день рождения, изображение профиля или предпочтения пользователя.
Модели, созданные в Flask-SQLAlchemy, представляются классами, которые преобразуются в таблицу в базе данных. Атрибуты этих классов превратятся в столбцы этих таблиц.
Давайте вместе создадим эту пользовательскую модель:
- nano project/models.py
Этот код создает пользовательскую модель со столбцами id
, email
, password
и name
:
from . import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
Мы создали пользовательскую модель и теперь можем перейти к настройке базы данных.
Как указано в разделе «Предварительные требования», мы будем использовать базу данных SQLite. Мы можем создать базу данных SQLite самостоятельно, но сейчас используем для этого Flask-SQLAlchemy. Мы уже указали путь к базе данных в файле __init__.py
, так что нам нужно просто указать Flask-SQLAlchemy создать базу данных на Python REPL.
Если вы остановите приложение и откроете Python REPL, мы сможем создать базу данных, используя метод create_all
для объекта db
. Убедитесь, что вы все еще находитесь в виртуальной среде и в каталоге flask_auth_app
.
- from project import db, create_app, models
- db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.
Примечание. Если вы незнакомы с использованием интерпретатора Python, вы можете проконсультироваться с официальной документацией.
Теперь вы видите файл db.sqlite
в каталоге проекта. В этой базе данных будет наша пользовательская таблица.
Для нашей функции регистрации мы возьмем данные, вводимые пользователем в форму, и добавим их в нашу базу данных. Прежде чем добавить это, нам нужно убедиться, что пользователя еще нет в базе данных. Если его нет, нам нужно хэшировать пароль, прежде чем поместить его в базу данных, потому что мы не хотим хранить пароли в формате обычного текста.
Для начала давайте добавим вторую функцию для обработки данных формы POST. В этой функции мы вначале соберем данные, которые были переданы пользователем.
Создайте функцию и добавьте в ее конец переадресацию. В результате после успешной регистрации пользователь будет переадресован на страницу входа.
Обновите файл auth.py
, изменив строку import и реализовав signup_post
:
from flask import Blueprint, render_template, redirect, url_for
...
@auth.route('/signup', methods=['POST'])
def signup_post():
# code to validate and add user to database goes here
return redirect(url_for('auth.login'))
Теперь добавим остальной код, необходимый для регистрации пользователя.
Для начала нам нужно будет использовать объект запроса для получения данных формы.
Продолжим обновлять файл auth.py
, добавляя элементы импорта и реализуя signup_post
:
from flask import Blueprint, render_template, redirect, url_for, request
from werkzeug.security import generate_password_hash, check_password_hash
from .models import User
from . import db
...
@auth.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
name = request.form.get('name')
password = request.form.get('password')
user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
if user: # if a user is found, we want to redirect back to signup page so user can try again
return redirect(url_for('auth.signup'))
# create a new user with the form data. Hash the password so the plaintext version isn't saved.
new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
# add the new user to the database
db.session.add(new_user)
db.session.commit()
return redirect(url_for('auth.login'))
Примечание. Хранение паролей в формате обычного текста считается плохой практикой с точки зрения безопасности. Обычно для защиты паролей нужно использовать сложный алгоритм хэширования и шифрование паролей.
У нас готов метод регистрации, и теперь мы можем создать нового пользователя. Используйте форму для создания пользователя.
Существует два способа проверить регистрацию: использовать инструмент просмотра БД для поиска строки, добавленную в таблицу, или попробовать зарегистрироваться с тем же адресом электронной почты, и если вы получите сообщение об ошибке, это будет означать, что первый адрес электронной почты был сохранен правильно. Используем этот подход.
Мы можем добавить код, чтобы сообщить пользователю, что адрес электронной почты уже существует, а затем отправить пользователя на страницу входа. Вызывая функцию flash
, мы отправим сообщение для следующего запроса, в данном случае это запрос переадресации. На итоговой странице мы получим доступ к этому сообщение в шаблоне.
Вначале мы добавим элемент flash
, выводимый до возврата на страницу регистрации.
from flask import Blueprint, render_template, redirect, url_for, request, flash
...
@auth.route('/signup', methods=['POST'])
def signup_post():
...
if user: # if a user is found, we want to redirect back to signup page so user can try again
flash('Email address already exists')
return redirect(url_for('auth.signup'))
Чтобы добавить в шаблон мигающее сообщение, нужно добавить над формой этот код. Так сообщение будет выводиться непосредственно над формой.
...
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="notification is-danger">
{{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>.
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/signup">
Метод входа похож на функцию регистрации тем, что мы берем информацию пользователя и что-то делаем с ней. В данном случае мы сравниваем введенный адрес электронной почты, чтобы проверить его наличие в базе данных. Если он там есть, мы протестируем указанный пользователем пароль, выполнив хэширование введенного пользователем пароля и сравнив его с хэшированным паролем в базе данных. Если оба хэшированных пароля совпадают, мы понимаем, что пользователь ввел правильный пароль.
После успешной проверки пароля мы знаем, что учетные данные пользователя верны, и мы можем разрешить ему вход в систему, используя Flask-Login. Вызывая login_user
, Flask-Login создает сеанс этого пользователя, который сохраняется все время, пока пользователь остается в системе, и позволяет пользователю просматривать защищенные страницы.
Мы можем начать с нового маршрута обработки данных, переданных через метод POST. Мы будем выполнять переадресацию на страницу профиля после успешного входа пользователя:
...
@auth.route('/login', methods=['POST'])
def login_post():
# login code goes here
return redirect(url_for('main.profile'))
Теперь нам нужно убедиться, что учетные данные пользователя введены верно:
...
@auth.route('/login', methods=['POST'])
def login_post():
email = request.form.get('email')
password = request.form.get('password')
remember = True if request.form.get('remember') else False
user = User.query.filter_by(email=email).first()
# check if the user actually exists
# take the user-supplied password, hash it, and compare it to the hashed password in the database
if not user or not check_password_hash(user.password, password):
flash('Please check your login details and try again.')
return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
# if the above check passes, then we know the user has the right credentials
return redirect(url_for('main.profile'))
Добавим в шаблон блок, чтобы пользователь мог видеть мигающее сообщение. Как и в случае с формой регистрации, добавим потенциальное сообщение об ошибке непосредственно над формой:
...
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="notification is-danger">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/login">
Теперь мы можем указать, что пользователь успешно выполнил вход, но пользователю пока некуда входить. На этом этапе мы используем Flask-Login для управления сеансами пользователя.
Прежде чем начать, нам потребуется несколько вещей для работы Flask-Login. Для начала добавьте UserMixin
в пользовательскую модель. UserMixin
добавит в модель атрибуты Flask-Login, чтобы Flask-Login мог с ней работать.
from flask_login import UserMixin
from . import db
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
Затем нам нужно указать загрузчик пользователя. Загрузчик пользователя сообщает Flask-Login, как найти определенного пользователя по идентификатору, сохраненному в файле cookie сеанса. Мы можем добавить его в функцию create_app
вместе с кодом init
для Flask-Login:
...
from flask_login import LoginManager
...
def create_app():
...
db.init_app(app)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
from .models import User
@login_manager.user_loader
def load_user(user_id):
# since the user_id is just the primary key of our user table, use it in the query for the user
return User.query.get(int(user_id))
В заключение мы можем добавить функцию login_user
перед переадресацией на страницу профиля для создания сеанса:
from flask_login import login_user
from .models import User
...
@auth.route('/login', methods=['POST'])
def login_post():
...
# if the above check passes, then we know the user has the right credentials
login_user(user, remember=remember)
return redirect(url_for('main.profile'))
С помощью Flask-Login мы можем использовать маршрут /login
. Когда все размещено правильно, вы увидите страницу профиля.
Если ваше имя не Anthony, вы увидите, что ваше имя указано неправильно. Нам нужно, чтобы профиль отображал имя в базе данных. Вначале следует защитить страницу, а затем получить доступ к данным пользователя для получения его имени.
Чтобы защитить страницу при использовании Flask-Login, мы добавим декоратор @login_requried
между маршрутом и функцией. Это не даст пользователю, не выполнившему вход в систему, увидеть этот маршрут. Если пользователь не выполнил вход, он будет переадресован на страницу входа согласно конфигурации Flask-Login.
Используя маршруты с декоратором @login_required
, мы можем использовать объект current_user
внутри функций. Этот объект current_user
представляет пользователя из базы данных, и мы можем получить доступ ко всем атрибутам этого пользователя, используя точечную нотацию. Например, current_user.email
, current_user.password
, current_user.name
и current_user.id
будут возвращать реальные значения, хранящиеся в базе данных для пользователя, который выполнил вход в систему.
Давайте используем имя текущего пользователя и отправим его в шаблон. Затем мы используем это имя и выведем его значение.
from flask_login import login_required, current_user
...
@main.route('/profile')
@login_required
def profile():
return render_template('profile.html', name=current_user.name)
Затем в файле profile.html
мы обновим страницу для отображения значения name
:
...
<h1 class="title">
Welcome, {{ name }}!
</h1>
Когда мы перейдем на страницу профиля, мы увидим, что там отображается имя пользователя.
В заключение мы можем обновить представление выхода из системы. Мы можем вызвать функцию logout_user
в маршруте выхода. Мы используем декоратор @login_required
, потому что не имеет смысла выполнять выход для пользователя, который не выполнил вход.
from flask_login import login_user, logout_user, login_required
...
@auth.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('main.index'))
После выхода и повторной попытки просмотра страницы профиля мы должны увидеть сообщение об ошибке. Это связано с тем, что Flask-Login выводит мигающее сообщение, когда пользователю не разрешен доступ к странице.
В заключение мы поместим в шаблоны выражения if
, чтобы отображать только ссылки, актуальные для пользователя. До входа в систему у пользователя должна быть возможность выполнить вход или зарегистрироваться. После входа у него должна быть возможность перейти в свой профиль или выйти из системы:
...
<div class="navbar-end">
<a href="{{ url_for('main.index') }}" class="navbar-item">
Home
</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.profile') }}" class="navbar-item">
Profile
</a>
{% endif %}
{% if not current_user.is_authenticated %}
<a href="{{ url_for('auth.login') }}" class="navbar-item">
Login
</a>
<a href="{{ url_for('auth.signup') }}" class="navbar-item">
Sign Up
</a>
{% endif %}
{% if current_user.is_authenticated %}
<a href="{{ url_for('auth.logout') }}" class="navbar-item">
Logout
</a>
{% endif %}
</div>
Мы успешно создали приложение с аутентификацией.
Мы использовали Flask-Login и Flask-SQLAlchemy для создания системы входа в наше приложение. Мы рассказали о том, как организовать аутентификацию пользователей посредством создания пользовательской модели и сохранения данных пользователя. Затем нам нужно было проверить правильность пароля пользователя, выполнив хэширование пароля из формы, и сравнив его с сохраненным в базе данных. В заключение мы добавили в приложение авторизацию, используя декоратор @login_required
на странице профиля, чтобы пользователи могли видеть ее только после входа.
Того, что мы создали в этом учебном модуле, будет достаточно для небольших приложений, но если вы хотите с самого начала использовать больше функций, подумайте об использовании библиотек Flask-User или Flask-Security, которые построены на базе библиотеки Flask-Login.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Я не понимаю, что делает строка
в файлах auth.py, models.py. Ведь в папке проекта нет файла db.py. Это потому что db определяется в файле init.py кодом
db = SQLAlchemy()
???Шаг 9 файл project/auth.py имеется ошибка в переменной в функции: login_user(user, remember=remember)
remember - принимает bool значения (True, False) По стандарту стоит False и определяет оставлять ли пользователя после истечения времени его сессии.