ru:https://highload.today/blogs/chto-takoe-avtorizatsionnyj-server-i-kak-ego-pravilno-vybrat-gajd-osnovannyj-na-moem-opyte/ ua:https://highload.today/uk/blogs/shho-take-avtorizatsijnij-server-ta-yak-jogo-pravilno-obrati-gajd-zasnovanij-na-moyemu-dosvidi/
logo
Опыт      15/07/2022

Что такое авторизационный сервер и как его правильно выбрать: гайд, основанный на моем опыте

Дмитро Богдан BLOG

.NET Solution Architect в NIX

Эта статья основана на моем опыте участия в одном из проектов.

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

Авторизационный сервер, айдентити-сервер, аус-сервер — под этими понятиями обычно подразумевают software, который на основе юзер-логина и юзер-пароля выдает токен или что-то вроде авторизации в системе.

В этой статье вы можете заметить разные термины, но различий между ними нет.

Нам достался legacy-монолит…

Наша история выбора авторизационного сервера началась с работы над одним из древних проектов — legacy-монолита на .NET 3.5. Авторизация была выполнена в виде обычной страницы на ASP, где пользователь вводит логин и пароль. В code-behind была установлена ​​сессия, куда записывалась необходимая информация. Это была аутентификация и авторизация пользователя.

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

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

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

Новый порядок — переход к микросервисам

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

Онлайн-курс "Продуктова аналітика" від Laba.
Станьте універсальним аналітиком, опанувавши 20+ інструментів для роботи з будь-яким продуктом.
Дізнатись більше про курс

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

Для PoC-проекта мы придумали следующую схему. Есть фронтенд и гейтвей, под ним находится оркестратор, связанный с микросервисами. Программа получает запрос от фронтенда и на его основе делает запросы к соответствующим микросервисам:

Наша команда сделала фронтед на Angular, сервисы и оркестратор – на .NET 6, а также добавила API Gateway, работавшего как AuthGuard.

Если пользователь не авторизован, гейтвей не пропускает запрос и возвращает ошибку 401. При этом легаси оставался таким, как был. Мы не имели не то что возможности, а даже цели оверинженериты. Нам нужен был только PoC. Поэтому для авторизации мы использовали токен из легаси. Для этого добавили в проект новую функциональность. Она генерировала токен, а фронтенд забирал его и отправлял рекламировать с ним через API Gateway. Тот, в свою очередь, парсил токен для оценки валидности и пропускал запрос дальше. Эксперимент показал хорошие результаты. Мы постепенно перевели часть системы на микросервисы и это положительно оценили все стороны проекта.

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

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

Мы задумались о подготовке какого-то long-term-решения. Это позволило закрыть вопросы авторизации и аутентификации в системе, применить одинаковый подход на всех микросервисах и на всех текущих и будущих клиентах. Но переезд требовал инфраструктурных изменений. Потому стало понятно: качественная аутентификация и авторизация необходимы здесь и сейчас.

Онлайн-курс "Маркетолог" від Laba.
Пройдіть повний шлях розробки маркетингових стратегій на практиці та з фідбеком від CEO бренд-маркетингової агенції.
Програма курсу і реєстрація

Всегда держите в голове базовые термины

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

  • Аутентификация. Это процесс в системе, который должен найти ответ на вопрос «Кто ты?». В этот момент сервер распознает пользователя.
  • Авторизация. Ее часто путают с аутентификацией, считая чуть ли не синонимом, но эти термины имеют разные значения. В этом случае система определяет, к каким ресурсам имеет доступ узнаваемый пользователь.
  • OAuth 2.0 и OpenID Connect. Это протоколы и спецификации, говорящие о том, как правильно аутентифицировать и авторизовать пользователя.
  • Grant Types. Часть протокола OAuth 2.0. Показывает, что нужно сделать для получения аксес-токена: какие запросы отправить, куда, когда, к какой базе, какие должны быть респонсы запросов и т.д.
  • Клиенты. Программы, которые дают запрос на защищенные данные. Чаще всего это фронтенд: мобильные приложения, веб-приложения, десктопные программы. Хотя клиентом может быть и бэкенд.
  • Ресурсы. Программы в виде разнообразных API, защищающих данные по OAuth-протоколу.
  • JWT-токен. То, что получает клиент для доступа к защищенному API. Грубо говоря, это как билет на поезд: если он у вас есть, вас пропускают в вагон и вы едете дальше.

Читайте также: Подделка невозможна: как устроен токен и какие задачи можно решить с помощью JWT-авторизации

Онлайн-курс "Стратегічний маркетинг" від Laba.
Навчіться збільшувати ROI компанії, відстежувати тренди просування та завойовувати нові ринки завдяки комплексній маркетинговій стратегії.
Програма курсу і реєстрація

Grant Types

Отдельно хочу поговорить об этой части протокола OAuth. Существует несколько часто используемых грант-тайпов:

  • Password Grant Type;
  • Implicit Flow;
  • Authorization Flow;
  • Client Credentials.

Указанные модели закрывают все основные пойнты. Таким образом вы можете разобраться, какие из них и в каких ситуациях следует использовать.

Password Grant Type

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

Как происходит процесс получения токена в Password Grant Type? Представим пользователя, переходящего на клиента (допустим, фронтенд-приложение). На клиенте есть специальная страница, где пользователь вводит свои логин и пароль. После этого клиент посылает на айдентити-сервер следующую информацию: ClientSecret, ClientName, UserName и т.д. Сервер после сравнения данных возвращает аксесс-токен. Это легкий путь получения доступа, ведь нужны только настроенные на сервере ClientId и ClientSecret, полученные от пользователя UserName и UserPassword, а также Scopes — идентификаторы определения доступных пользователю данных.

Как видите, модель достаточно быстра для реализации. Благодаря этому в свое время Password Grant Type стандартизировал процесс получения аксес-токена. До этого можно наблюдать зоопарк различных механизмов того, как, где и почему использовать UserName и UserPassword. Иногда доходило до того, что логин и пароль отправлялись почти в незашифрованном виде в хедерах в реквест. Сегодня же Password Grant Type уже устарел. Его ключевой недостаток — пробелы в безопасности. Вы можете заметить, что в этой схеме клиент может где-то хранить логины и пароли. При этом эндпойнт из-за стандартизации грант-тайпа хорошо известен. Поэтому злоумышленники могут попытаться установить в систему NPM-пакет для сбора всех данных и перехватить выдаваемый токен.

Курс-професія "Junior Data Analyst" від robot_dreams.
Комплексний курc для всіх, хто хоче опанувати нову професію з нуля.На прикладі реальних датасетів ви розберете кожен етап аналізу даних.
Програма курсу і реєстрація

Так преступник узнает, где находится сервис авторизации, а также узнает UserName и UserPassword. В результате система будет скомпрометирована. Более того: хакер будет постоянно получать валидный токен, потому что будет иметь логин и пароль. Также существует проблема хранения ClientId и ClientSecret на стороне клиента. Это не самое безопасное решение для фронтенда, поскольку здесь не осуществляется никаких редиректов. Привязка идет только на основе этих параметров. Если их украсть, новый клиент сможет подключить пользователя на сторонний сайт и ввести ClientId и ClientSecret. И тогда пользователи будут логиниться там и получать аксес-токен, который также будет угнан. Все это перечеркивает предыдущие преимущества Password Grant Type.

Implicit Flow

Этот подход более безопасен, поэтому некоторое время он был очень популярен. Implicit Flow достаточно удобен, его можно установить без особого труда. Однако у него есть несколько проблем с защищенностью, которые сегодня делают этот механизм в протоколе OAuth 2.0 нежелательным. Для понимания проблемы разберем флоу пошагово:

  • Пользователь обращается к клиенту, фронтенд-приложению и нажимает кнопку «Логин».
  • После этого клиент осуществляет пользовательский редирект на страницу /authorize на айдентити-сервер. Этот эндпойнт показывает пользователю свою страницу для ввода логина и пароля. Если они верны, аус-сервер выдает клиенту аксес-токен:

  • На первый взгляд у Implicit Flow все хорошо. Здесь только одна логин-страница: мы сами ее написали, и наш сервер авторизации ответственен за нее. Для сравнения при наличии пяти клиентов в Password Grant Type требуется пять отдельных страниц для ввода логина и пароля. А здесь достаточно настроить редирект на единую логин-страницу для любого количества клиентов.

Но у этой схемы есть существенный недостаток. В Implicit Flow все построено на редиректах, то есть на GET-запросах. Поэтому на финальном шаге клиент получает аксес-токен из Query-параметров. Поэтому вредоносное программное обеспечение на клиенте может получить токен из кода так же, как мы это делаем с помощью библиотек для получения доступа к системе. А это уже очень серьезная проблема. Хотя гораздо хуже другое – риск похищения рефреш-токена. Аксесс-токен предоставляет доступ на 10 минут. Это много, но ограничивает злоумышленника, заставляя его повторять свои действия. С рефреш-токеном хакер свободен. Этот токен лишает пользователя необходимости повторного редиректа на айдентите сервер после окончания сессии.

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

Authorization Flow

Это рекомендованный OAuth-подход. Его сценарий сродни Implicit Flow: пользователь делает логин-клик на клиента и перенаправляется на /authorize на айдентите-сервер. Последний после подтверждения валидности логина и пароля возвращает не аксесс- и рефреш-токены, а авторизационный код в URL. Далее клиент отправляет на айдентити-сервер POST-запрос с ClientId, ClientSecret и Authorization Code и получает ответный POST-реквест с аксесс- и рефреш-токенами.

Онлайн-курс "Створення текстів" від Skvot.
Великий практичний курс для розвитку скілів письма та створення історій, які хочеться перечитувати Результат курсу — портфоліо з 9 робіт та готовність братися за тексти будь-яких форматів.
Детальніше про курс

Из-за того, что такой реквест более защищен, чем Query-параметры, вся схема получается более надежной:

Есть идеальный флоу для получения аксес- и рефреш-токенов, но всегда есть «но».

Главный вопрос уже к команде, которая интегрируется с этим флоу: где хранить аксесс- и рефреш-токены?

Если в памяти, то при повторном входе нужно заново авторизироваться. А если положить токены в cookies, session или local storage, то вредоносное malware-ПО легко украдет их. В результате получение доступа устроено гораздо безопаснее, чем раньше, но хранение доступов уязвимо. Я думаю, многие сталкивались с этой проблемой. Каждый решает ее по-своему, потому дальше я расскажу, как мы решили этот вопрос.

Client Credentials

Раньше я описывал схемы получения доступа к API от клиента (web, desktop, mobile). Client Credentials необходим для доступа одной программы API к другой API.

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

Такая простота возможна потому что при взаимодействии API не требуется интерактивность – ее практически некуда добавить. Апишки работают без фронтендов.

Курс UX/UI дизайнер сайтів і застосунків з Alice K.
Курс від практикуючої UI/UX дизайнерки, після якого ви знатимете все про UI/UX дизайн .
Реєстрація на курс

К тому же сама по себе API — это защищенная часть системы, куда не так просто попасть. Поэтому мы можем спокойно записать параметры в сеттинг или в память и при необходимости достать их оттуда для аус-сервера, чтобы он выдал токен.

Выбор авторизационного сервера

После оценки основных авторизационных флоу можно переходить к выбору сервера. Сначала наша команда остановилась на трех кандидатах:

Каждый из них интересен по-своему.

Например, Identity Server 4 — это опенсорсный продукт, где можно легко настраивать разные моменты. Лично нашей команде он был хорошо знаком по предыдущим проектам.

Опенсорный сервер и Keycloak — один из самых популярных на сегодняшний день. Хотя мы были готовы к выбору платной программы, дающей все механизмы OAuth-протокола «из коробки». В этом случае нас заинтересовал Cognito, поскольку у  нас была инфраструктура на AWS. При выборе сервера для нас важны были следующие параметры:

  • возможности разнообразных флоу;
  • Онлайн-курс "Excel та Power BI для аналізу даних" від robot_dreams.
    Навчіться самостійно аналізувати й візуалізувати дані, знаходити зв’язки, розуміти кожен аспект отриманої інформації та перетворювати її на ефективні рішення.
    Детальніше про курс
  • поддержка SSO, Single Sign-On;
  • хорошее соотношение цены и качества продукта;
  • гибкие изменения и адаптации флоу под свои нужды;
  • доступность использования кастомных решений;
  • простой и удобный механизм для миграции пользователей.

Keycloak

К его преимуществам относятся простая и гибкая настройка, поддержка SSO и кастомных флоу и админпанель прямо «из коробки», что экономит много времени. Но как минимум для нашей команды, Keycloak имеет минусы:

  1. Достаточно высокий порог вхождения. Если у вас есть схожий опыт работы с серверами, то будет проще. Но у нас такого специалиста не было.
  2. С Keycloak нужно позаботиться о хостинге (хотя, да, это неудобство еще можно решить).
  3. Source-code в Keycloak на Java. В нашем случае на проекте не было Java-разработчика.
  4. Онлайн-курс "Digital Marketing" від Laba.
    Розширте пул навичок у роботі з аудиторією.Навчіться запускати рекламні кампанії без зайвих витрат бюджету з сучасними інструментами діджитал-маркетингу, включаючи AI.
    Детальніше про курс

При оценке этих характеристик мы помнили, что разрабатываем long-term support продукт. Если бы в дальнейшем возникли проблемы или нам просто было непонятно поведение системы, то пришлось бы что-то менять в коде. Это привело бы к серьезным проблемам и задержкам, поэтому мы отказались от Keycloak.

Cognito

У этого продукта много преимуществ. К примеру, это дешевое решение для нашего количества пользователей: активных и запланированных на будущее. Также у Cognito очень быстрый этап. Вы просто создаете User Pool и клиентов — и фронтенд-приложение фактически готово к авторизации. Сервер сразу можно использовать.

Но и у Cognito есть ряд недостатков, или, скорее, подозрений на недостатки.

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

Появились некие трудности при передвижении юзеров. Допустим, пользователь впервые входит в систему. Она перенаправляет его на страницу Cognito для ввода логина и пароля и проверяет, существует ли такой пользователь в пользовательском пуле. Его, конечно, нет, он ведь впервые здесь. Тогда на следующем этапе должна быть лямбда-функция, которая обращается к легаси-применению с введенными логином и паролем и проверяет их валидность. Если данные ошибочны, выдается Error. Если все правильно, то AWS, запомнив еще на первом этапе логин и пароль, создает пользователя в пользовательском пуле после подтверждения лямбда-функцией всех данных — и возвращает аксесс-токен:

Теперь представим, что пользователь снова входит в систему и вводит уже знакомые для AWS логин и пароль. AWS проверяет существование пользователя в пользовательском пуле (например, по мейлу), в случае положительного результата сверяет логин и пароль и при условии их правильности выдает аксесс-токен. Незамысловатая и эффективная схема.

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

Из-за всех этих недостатков мы решили не брать Cognito.

Курс Frontend від Mate academy.
Frontend розробник може легко створити сторінки вебсайту чи вебдодаток. Тому після курсу ви станете затребуваним фахівцем у сфері, що розвивається.
Інформація про курс

Identity Server 4

Наша команда уже имела опыт работы с Identity Server 4, то поначалу это могло показаться «тем самым» решением. Этот авторизационный сервер показал себя гибким, с простой интеграцией в .NET-приложение. Также к его безусловным достоинствам следует отнести наличие кастомных флоу, поддержку SSO и хорошую документацию. Здесь тоже нужно позаботиться о хостинге, но более серьезным недостатком является прекращение поддержки этого продукта после 22 ноября 2022 года. Учитывая долгосрочную перспективу развития нашего проекта, подобное ограничение было неприемлемым. Мы двинулись дальше.

Duende

Спасением для нас стал авторизационный сервер. Duende — это практически продолжение Identity Server 4 со всеми его преимуществами, но без проблем «22 ноября». Также есть BFF Security Framework. Подробную информацию о нем можете почитать на официальном сайте.

Я выделю самые интересные моменты:

  • Нет токенов в браузере. Аксесс- и рефреш-токены полностью защищены от просмотра JS-кодом. Для этого к фронтенду добавляют бэкенд, создающий сессию и cookies. При этом бэкенды с фронтендом расположены на одном домене, поскольку первый является частью второго. В результате бэкенд может сетить куки под флагом HttpOnly. Далее они при каждом запросе фронтенда добавляются в реквест и идут на бэкенд, который проведет аутентификацию и авторизацию пользователя. Это заметно безопаснее генерации cookies с фронтенда, где их может перехватить «чужой» JS-код.
  • Предоставление API для авторизационных эндпойнтов. В обычной схеме авторизации фронтенд посылает запрос с набором из ClientId, ClientSecret, с реквестами и добавлением OAuth, возвращает ответы с кодом типа 401 и т.д. Все это нам нужно тщательно выстраивать на клиенте. С помощью BFF Security Framework можно создать для фронтенда несколько эндпойнтов, допустим, BFF/Login. Как только фронтенд перейдет на него, фреймворк сам сделает все настройки для перередиректа на айдентите-сервере, авторизации и возвращения назад, обработки реквеста и сохранения аксесс- и рефреш-токенов в форме кук с флагом HttpOnly. Благодаря этому на стороне фронтенда можно вообще не беспокоиться о авторизации. Весь процесс автоматический: достаточно отправить BFF/Login или BFF/Logout с подставленной сессией для входа или выхода из системы.
  • Управление сессиями. В BFF Security Framework есть отдельный модуль для тонкой настройки бэкенд-сессии. Это может пригодиться во многих ситуациях. К примеру, менеджмент сессий понадобится при очень больших cookies. Также он поможет для этапа Backchannel logout, когда пользователь распространяется с одной части системы или из всех.
  • Менеджмент токенов. Этот фреймворк предоставляет множество API для управления и смены токенов. К примеру, можно получить аксесс-токен и при необходимости переработать или доработать его.
  • Быстрая и легкая интеграция. BFF Security Framework это действительно не GET-пакет. Практически все предоставленные функциональности легко переопределяются. Например, можно кастомизировать эндпойнты для логина и логаута. Также можно хранить сессию как в MSSQL, так и сделать свое хранилище. То же касается и упомянутого токен-менеджмента.

Все это позволило нам принять правильное решение. Duende оказался оптимальным авторизационным сервером для наших задач.

Бізнес англійська від Englishdom.
Тут навчають за методикою Кембриджу, завдяки якій англійську вивчили понад 1 мільярд людей. Саме вона використовується в найкращих навчальних закладах світу, і саме за нею створені курси.
Інформація про курс

Результат

Итак, у нас получилась приведенная ниже схема. В продукте есть один айдентити-сервер и несколько фронтендов. На схеме их три для простоты оценки модели, но по сути таких частей еще больше.

При этом мы убрали API Getway и фронтенд общается непосредственно с оркестратором. К оркестратору мы добавили BFF Security Framework. Все хостится на одном домене — это требование BFF Security Framework. За счет этого токен предоставляется в оркестратор, что позволило построить еще и antiforgery-защиту фронтенда. Далее оркестратор аутентифицирует и авторизирует пользователя и отправляет рекламу на микросервис. Все просто и, что самое важное, надежно:

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

  • Вместо того чтобы придумывать велосипед с PoC и «кастомным» авторизационным сервером, который выдает токены, мы пришли к стандартизированному подходу для этой сферы. Поэтому сейчас наш продукт легко интегрирован с другими решениями.
  • Используем одинаковый подход для разных клиентских приложений. После настройки флоу для первого клиента добавлять 2, 3, 5, 10 и все остальные уже не будет проблемой. За счет этого сокращается время интеграции новых клиентов.
  • Есть актуальный рекомендованный OAuth-протоколом принцип Authorization Grant Type.
  • В нашем случае BFF Security Framework показал себя хорошим решением, которое на сегодняшний день покрывает все наши потребности и экономит время. С ним мы больше не храним аксесс- и рефреш-токены в браузере. Используем практически весь его функционал: у нас есть и Backchannel logout, и бэкенд-сессия. Авторизацию и аутентификацию убрали с фронтенда.

Конечно, в вашем случае все может сложиться иначе, и вы выберите, скажем, Keycloak. Более того, если вам не нужны дополнительные механизмы редиректов и вообще нет сложной логики, тогда и Cognito или Azure Active Directory отлично подойдут. Поэтому при выборе айдентити-сервера, прежде всего, советую грамотно оценивать свои задачи и возможности каждого существующего авторизационного сервера.

Онлайн-курс "Архітектура високих навантажень" від robot_dreams.
Досвід та інсайти від інженера, який 12 років створює програмне забезпечення для Google.
Програма курсу і реєстрація

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Курс Power Skills For Tech від Enlgish4IT.
Зменшіть кількість непорозумінь на робочому місці та станьте більш ефективним у спілкуванні в мультикультурній команді. Отримайте знижку 10% за промокодом ITCENG.
Реєстрація на курс

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Топ-5 самых популярных блогеров марта

PHP Developer в ScrumLaunch
Всего просмотровВсего просмотров
2434
#1
Всего просмотровВсего просмотров
2434
Founder at Shallwe, Python Software Engineer (Django/React)
Всего просмотровВсего просмотров
113
#2
Всего просмотровВсего просмотров
113
Career Consultant в GoIT
Всего просмотровВсего просмотров
95
#3
Всего просмотровВсего просмотров
95
CEO & Founder в Trustee
Всего просмотровВсего просмотров
94
#4
Всего просмотровВсего просмотров
94
Рейтинг блогеров

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: