
Записки вкатуна
8 постов
8 постов
7 постов
Термины в статье:
К стеку добавился:
Vue.js
Потратив 2 ночи (и немного терпения), я наконец завершил реализацию всех эндпоинтов! В какой-то момент это очень наскучило, но я все равно продолжал писать их.
Как выглядят эндпоинты в этой статье - Создаю онлайн-сервис для чтения книг. День 4. Обработка первого запроса.
Архитектура папок теперь выглядит вот так:
Всего реализовано 38 эндпоинтов , и это еще не конец — их количество будет расти! Последний из них ощущался как будто я пробежал марафон.
Но как делать веб без дизайна? Верно, никак! 🤔
Зайдя в Figma и подсмотрев интерфейс GitHub'а , я накидал ориентировочный дизайн страниц регистрации и входа.
Верстка — это немного рутинное занятие, но результат того стоит!
Могу сказать, что получилось почти идентично дизайну.
Почему Vue.js ? Просто такие условия 🙃. Если бы выбор был за мной, я бы взял React .
Добавлю в решение новый проект Веб-приложение ASP.NET Core (MVC) . Стандартный шаблон создаст такие папки:
wwwroot
Это корневая папка для статических файлов, которые будут доступны напрямую через браузер.
Controllers
Папка содержит классы контроллеров, которые управляют маршрутизацией.
Models
Папка содержит модели данных, которые представляют сущности приложения.
Views
Папка содержит представления — файлы, которые отвечают за отображение HTML-страниц пользователю.
В папку wwwroot/lib добавлю клиентскую библиотеку Vue.js .
Создам новый контроллер для страниц аутентификации.
В папке Views есть еще 2 папки:
Home
Shared
В папку Shared добавлю новый шаблон страницы Razor с названием _LoginLayout для страниц аутентификации.
Создам папку Auth, добавив туда пустую страницу Razor с названием _ViewStart. Этот файл указывает какой шаблон будут использовать страницы.
@{
Layout = "_LoginLayout";
}
Теперь закину в wwwroot/css свой файл со стилем, который наверстал.
Начну со страницы входа, добавив HTML-разметку, которую наверстал.
Снизу файла напишу блок скриптов:
@Section scripts {
<script src="~/lib/vue/vue.global.js"></script>
<script>
...тут будут все скрипты...
</script>
}
Пример запроса на бэкенд:
На скрине показан метод ассинхронный метод login(), в нем реализована отправка запроса на эндпоинт /auth/login, если запрос проходит успешно, я записываю Access-токен в локальное хранилище и перенаправляю пользователя на страницу по адресу /home. В противном случае я показываю ошибку пользователю.
В ответе сервера я получаю Access и Refresh токены.
Access-токен записываю в локальное хранилище.
Refresh-токен храню в Http-only куках для большей безопасности 🛡️.
Пример записи куки на сервере:
Здесь я записал новую куку с под названием refreshToken, и значением, равным Refresh-токену.
Вообще для чего мне Refresh-токен, если есть Access? Все очень просто, у Access-токена срок жизни 15 минут, поэтому через 15 минут его необходимо будет сгенерировать заново. Для этого как раз и понадобится Refresh-токен.
По истечению Access-токена я буду посылать на сервер запрос со своими куками. Сервер прочитает Refresh-токен из них и, если он валидный, вернет новый Access-токен.
Как это реализовано со стороны сервера:
Со стороны клиента:
При загрузке страницы сразу же срабатывает метод checkRefreshToken, далее отправляется запрос на /auth/refresh. Если сервер возвращает положительный ответ, записываю новый Access в локальное хранилище и продолжаю пользоваться сервисом.
Запущу бэкенд и фронтенд.
После запуска я сразу попадаю на страницу входа.
Войду в аккаунт, который я создвал еще на начальных этапах.
Если сейчас обратиться по адресу /home, меня перекинет назад на страницу авторизации.
Попробую сначала ввести неправильный пароль.
Теперь введу правильный пароль. Все сработало! Я попал на домашнюю страницу.
Зайдя в консоль разработчика, можно посмотреть локальное хранилище и найти там Access-токен.
Теперь я могу пользоваться сервисом, отправляя запросы на эндпоинты, передавая данный токен в заголовке.
🎁 Бонусная статья.
Создам таблицу, которая будет хранить коды подтверждения. В ней будут следующие столбцы:
id: уникальный идентификатор
code: сгенерированный код
email: почта, на которую пришел код
expires_in: дата и время истечения кода
Опишу методы работы с этой базой данных через интерфейс.
В интерфейс добавлю логику для отправки кода на почту.
Отправляться будет шестизначный код. Генерация кода выглядит так:
Теперь зайду в mail.ru и создам новую почту. Перейду в настройки безопасности и выберу пункт "Пароли для внешних приложений" . Создам новое внешнее приложение и получу для него пароль. Запишу его себе куда-нибудь.
Для отправки сообщений необходимо подключить библиотеку System.Net.Mail. Она поможет настроить SMTP-клиент и отправить письмо.
Эти переменные необходимы для настройки подключения к SMTP-серверу, который будет отправлять письмо.
Описание : Адрес SMTP-сервера, который используется для отправки писем.
Пример :
Для Gmail: smtp.gmail.com
Для Mail.ru: smtp.mail.ru
Для Yandex: smtp.yandex.ru
Описание : Порт, используемый для подключения к SMTP-серверу. Обычно это:
587 (для TLS/STARTTLS)
465 (для SSL)
Пример : 587 для Gmail или Mail.ru.
Описание : Логин (обычно email), который используется для аутентификации на SMTP-сервере.
Описание : Пароль для аутентификации на SMTP-сервере. В моем случае я вписал пароль, который недавно получил от mail.ru.
Класс MailMessage используется для создания самого письма. Вот его основные параметры:
Описание : Этот флаг указывает, что тело письма содержит HTML-разметку. Если установлено значение false, то тело письма будет обрабатываться как простой текст.
Описание : Указывает адрес отправителя. Это поле должно быть заполнено объектом типа MailAddress.
Описание : Указывает адрес получателя. Можно добавить несколько адресов с помощью метода message.To.Add().
Описание : Тема письма, которая отображается в заголовке сообщения.
Описание : Тело письма. Если IsBodyHtml = true, то здесь можно использовать HTML-разметку. В противном случае это будет простой текст. В моем случае я сверстал небольшую форму, записал её в отдельную переменную класса Constaints.
Создам эндпоинт, который будет отвечать за отправку сообщения. Например:
Запускаю сервер и тестирую функционал:
Ввожу адрес электронной почты, на который хочу отправить сообщение. 📝
Нажимаю "Выполнить". ✅
И... Письмо пришло! 🎉
Убеждаюсь, что запись добавлена в базу данных. Все отлично работает! ✅
Теперь пользователи могут подтвердить свою почту с помощью шестизначного кода.
Термины в статье:
Для поддержки доступа к сайту с нескольких устройств (веб, десктоп, мобильное приложение) в таблицу UserSessions были добавлены следующие столбцы:
device_type : Тип устройства (web, desktop, mobile)
created_at : Дата создания сессии
1. Удаление флага bool Flag
Ранее в ответах сервера использовался флаг bool Flag для обозначения успешности операции. Теперь обработка ошибок выполняется через блоки try-catch. Это делает код более читаемым и устойчивым к ошибкам.
В файл конфигурации appsettings.json добавлен новый раздел JwtSection, содержащий секретный ключ и время жизни токена:
Для удобства работы с JWT был создан вспомогательный класс, который хранит данные из раздела JwtSection.
В Program.cs этот класс подключается следующим образом:
builder.Services.Configure<JwtSection>(builder.Configuration.GetSection("JwtSection"));
Был создан интерфейс, описывающий логику работы с базой данных для управления сессиями.
Реализую данный интерфейс. 💻
Для генерации refresh-токена используется метод, который создает случайную строку из 32 байт:
Для генерации access-токена необходимы данные о пользователе (claims), секретный ключ из appsettings.json и срок действия токена:
В методе SignIn происходит следующее:
Генерируются Access и Refresh токены.
Проверяется наличие сессии в базе данных. Если сессия существует, обновляется Refresh-токен.
Если сессия не найдена, создается новая запись в таблице UserSessions.
Если срок действия Access-токена истек, пользователь может получить новый токен, используя Refresh-токен. Для этого реализован метод RefreshToken:
Здесь происходят базовые проверки, во избежание ошибок. Далее генерируется новый Access-токен. Если срок Refresh-токена истек, то генерирую новый и отправляю его пользователю вместе с Access-токеном.
Для защиты эндпоинтов от неавторизованных пользователей была добавлена логика обработки Access-токенов:
Здесь из appsettings.json достаются все данные из ячейки JwtSection и помещаются в переменную config. Это позволяет централизованно управлять секретным ключом и временем жизни токена.
Далее добавляется аутентификация, указывая схему аутентификации (JwtBearer) в качестве параметра. Для этого используется метод AddJwtBearer, который как раз и будет валидировать токен. В нем указываются следующие проверки:
Срок жизни токена : Убедиться, что токен не истек. ⏳
Ключ шифрования : Проверить, что токен был подписан с использованием корректного секретного ключа. 🔑
В Program.cs регистрируется метод AddAuth:
builder.Services.AddAuth(builder.Configuration);
Также добавлены строки для использования аутентификации и авторизации
app.UseAuthentication();
app.UseAuthorization();
Теперь необходимо защитить Endpoint. Защищенные эндпоинты помечаются атрибутом [Authorize]:
Регистрация проходит успешно, если данные пользователя уникальны.
После входа в систему пользователь получает Access и Refresh токены.
На сайте jwt.io можно проверить содержимое Access-токена. В поле Payload видны все Claims.
Получен статус-код 401, что означает "Доступ запрещен".
После добавления токена в заголовок Authorization запрос выполнен успешно (статус-код 200).
Все компоненты системы аутентификации и управления сессиями работают корректно. Далее буду крудошлепить остальную бизнес-логику по аналогии.
🥁 А темой для следующей статьи станет:
Подтверждение почты через отправку кода в письме
CRUD — это аббревиатура, обозначающая четыре основные операции:
Create (Создание) — добавление новых записей в базу данных.
Read (Чтение) — получение данных из базы.
Update (Обновление) — изменение существующих данных.
Delete (Удаление) — удаление записей.
Крудошлеп — это разработчик, который занимается реализацией этих базовых операций, часто без глубокого понимания архитектуры. Такие разработчики могут быть новичками или специализироваться на рутинных задачах, не требующих сложных решений.
Термины в статье:
Логи пишутся практически при каждом действии пользователей, поэтому начну с их реализации в качестве примера.
Перейду в проект HelpLibrary и создам новый класс в папке DTOs .
Создам DTO для логов:
DTO Logs
Теперь перейду в проект ServerLibrary и добавлю интерфейс ILogRepository для определения класса логов.
Реализую этот интерфейс, создав класс LogRepository в папке Implementations . Он содержит:
Поле _context, которое объявляется в конструкторе для контекста базы данных.
Реализацию метода WriteLogs, который записывает логи в базу данных.
💡 Важно: Такой шаблон будет использоваться для каждой таблицы базы данных.
Qwen2.5-Max1:29 am
Вот обновленная версия текста с добавлением стикеров для улучшения визуального восприятия и акцентирования внимания на ключевых моментах:
1. Добавление логов 📝
Логи пишутся практически при каждом действии пользователей, поэтому начну с их реализации в качестве примера.
Перейду в проект HelpLibrary и создам новый класс в папке DTOs .
Создам DTO для логов:
Теперь перейду в проект ServerLibrary и добавлю интерфейс ILogRepository для определения класса логов.
Реализую этот интерфейс, создав класс LogRepository в папке Implementations . Он содержит:
Поле _context, которое объявляется в конструкторе для контекста базы данных.
Реализацию метода WriteLogs, который записывает логи в базу данных.
💡 Важно: Такой шаблон будет использоваться для каждой таблицы базы данных.
Создам DTO для регистрации и входа в аккаунт:
Для регистрации обязательными полями являются: логин , имя , email и пароль .
Для входа в аккаунт необходимы: почта/логин и пароль .
Интерфейс репозитория 🔍
Определяю интерфейс репозитория для работы с базой данных. На данном этапе необходимо реализовать следующие методы:
Поиск пользователя по Id .
Поиск пользователя по никнейму .
Поиск пользователя по email .
Добавление пользователя в базу данных.
Реализация интерфейса ⚙️
Создаю интерфейс с методом регистрации и входа в аккаунт.
Реализую бизнес-логику, реализовав созданных интерфейс. В конструкторе подключаю классы для работы с базой данных.
Метод регистрации
Метод входа в аккаунт
Создам новый класс в папке Controllers проекта Server , назову его AuthController . Добавлю в конструктор класса только что созданный сервис.
Добавлю два endpoint'а:
POST /register — для регистрации нового пользователя.
POST /login — для входа в аккаунт.
📌 Примечание: Swagger автоматически покажет доступные эндпоинты после запуска проекта.
Запускаю проект. Открывается страница Swagger, где видны два созданных эндпоинта.
Регистрация 🆕
Открою POST /register и впишу в тело запроса:
{
"nickname": "vkatun",
"name": "vasya",
"email": "vasya@mail.ru",
"password": "password"
}
В итоге получу ответ с сообщением об успешной регистрации и статус-кодом 200!
🎉 Успех! Проверяю базу данных — в таблицах Users и Logs добавились соответствующие записи.
Все работает!
Вход в аккаунт 🔑
Тестирую POST /login с правильными данными:
{
"emailOrNickname": "vkatun",
"password": "password"
}
В итоге получу ответ об успешной аутентификации и статус-кодом 200.
Попробую ввести несуществующий логин и пароль:
{
"emailOrNickname": "vkatun123",
"password": "password"
}
и
{
"emailOrNickname": "vkatun",
"password": "password123"
}
Получу ответы:
{
"flag": false,
"message": "There is no account with this email or nickname."
}
и
{"flag": false,
"message": "Invalid password"
}
соответственно
⚠️ Ошибка обработана корректно!
Все работает корректно! Теперь можно приступить к реализации логики JWT, но это уже тема для другой статьи.
📚 Следите за обновлениями!
Endpoint — это конкретный URL-адрес на сервере, который обрабатывает определенный тип запросов от клиента. По сути, это точка входа для взаимодействия с API. Каждый endpoint связан с определенной логикой: получение данных, создание новых записей, обновление или удаление информации.
GET /users — получить список пользователей.
POST /users — создать нового пользователя.
PUT /users/{id} — обновить данные пользователя с указанным ID.
DELETE /users/{id} — удалить пользователя с указанным ID.
HTTP-метод : GET, POST, PUT, DELETE и другие.
Путь (URL) : уникальный адрес, который указывает на конкретный ресурс.
Логику обработки : код на сервере, который выполняется при обращении к этому endpoint.
Термин "ручки" появился в профессиональной среде аналитиков и разработчиков как неформальное обозначение endpoints. Это связано с тем, что каждый endpoint можно представить как "рычаг" или "кнопку", которую клиент "нажимает", чтобы получить доступ к определенному функционалу сервера.
Для реализации базовых функций используется .NET 7.0 со следующими компонентами:
Avalonia (для кроссплатформенного UI, если потребуется).
.NET Framework Core (базовые библиотеки и runtime).
.NET Framework Tools (инструменты разработки).
.NET Framework Design (шаблоны и архитектурные решения).
🏗️ Проектирование структуры решения
Решение состоит из трёх проектов:
1. Server (Веб-API ASP.NET Core)
Роль: Точка входа приложения.
Содержит:
Эндпоинты API,
Middleware,
Конфигурацию сервера.
Зависимости:
ServerLibrary,
HelpLibrary,
Swashbuckle.AspNetCore.Filters.
2. HelpLibrary (Библиотека классов)
Роль: Хранение сущностей БД и DTO.
Пример сущности:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
3. ServerLibrary (Библиотека классов)
Роль: Бизнес-логика и работа с БД.
Функционал:
Аутентификация,
Валидация данных,
Взаимодействие с базой данных.
Для ServerLibrary:
Microsoft.AspNetCore.Identity
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt
Для Server:
Swashbuckle.AspNetCore.Filters (для документации API).
Проект Server ссылается на:
ServerLibrary,
HelpLibrary.
Что это такое? - scaffolding
Выполнение команды:
Scaffold-DbContext "Server=.\SQLEXPRESS;Database=Readify;Trusted_Connection=True;encrypt=false;" Microsoft.EntityFrameworkCore.SqlServer
Распределение кода:
DbContext → перемещён в ServerLibrary,
Сущности → остаются в HelpLibrary.
В конечном итоге сгенерировались сущности и контекст базы данных.
Добавление строки подключения в appsettings.json:
"ConnectionStrings":
{
"DefaultConnection": "Server=.\\SQLEXPRESS;Database=Readify;Trusted_Connection=True;Encrypt=false;"
}
Регистрация контекста БД в Program.cs:
builder.Services.AddDbContext<ReadifyContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection") ??
throw new InvalidOperationException("Error to connection!"));
});
Сгенерированы сущности БД через EF Core Scaffolding.
Настроено подключение к SQL Server.
Реализована трёхслойная архитектура:
Server (API),
HelpLibrary (DTO/Entities),
ServerLibrary (Бизнес-логика).
Проект готов к реализации бизнес-логики и разработке API!
Продолжение следует...