Русский
Русский
English
Статистика
Реклама

Authentication

Тонкости авторизации обзор технологии OAuth 2.0

21.09.2020 18:14:10 | Автор: admin
Информационная система Dodo IS состоит из 44 различных сервисов, таких как Трекер, Кассы ресторана или Базы знаний и многих других. 3 года назад мы написали сервис Auth для реализации сквозной аутентификации, а сейчас пишем уже вторую версию. В основе сервиса лежит стандарт авторизации OAuth 2.0. Он довольно сложный, но если будете работать над аналогичным сервисом, стандарт вам пригодится. В этой статье я постарался рассказать о стандарте максимально просто и понятно, чтобы вы сэкономили время на его изучение.



Задача Auth


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

У сервиса Auth есть три основные задачи:

  • Единая точка аутентификации (SSO) для всех сервисов системы. Сервисы не хранят учётные данные, а доверяют это одному выделенному сервису.
  • Безопасный и гранулированный доступ к ресурсам. Безопасный, потому что пароли хранятся в одном месте и максимально защищены. Гранулированный, так как владельцы сервисов могут настраивать доступ к ресурсам как они захотят, опираясь на данные, пришедшие из сервиса аутентификации.
  • Централизованное управление пользователями и доступом. Благодаря тому, что вся информация о пользователе хранится в сервисе аутентификации, мы можем управлять пользователями централизованно.

Проблемы


Первая версия Auth часть монолита. Он использует свой собственный протокол общения с сервисами. Такая схема была необходима в тот момент, но за несколько лет работы проявились проблемы.

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

Dodo IS зависит от Auth. В старой реализации внешние сервисы обращаются к Auth при каждом действии пользователя, чтобы валидировать данные о нём. Настолько сильная привязка может привести к остановке работы всей Dodo IS, если Auth приляжет по какой-то причине.

Auth зависит от Redis. Притом достаточно сильно неисправность работы Redisа приведёт к падению Authа. Мы используем Azure Redis, для которого заявленный SLA 99,9%. Это значит, что сервис может быть недоступен до 44 минут в месяц. Такие простои не позволительны.

Текущая реализация Auth использует свой протокол аутентификации, не опираясь на стандарты. В большинстве своих сервисов мы используем C# (если говорим о backend) и у нас нет проблем с поддержкой библиотеки для нашего протокола. Но если вдруг появятся сервисы на Python, Go или Rust, разработка и поддержка библиотек под эти языки потребует дополнительных затрат времени и принесет дополнительные сложности.

Текущий Auth использует схему Roles Based Access Control, которая базируется на ролях. Обычно с ролью выдаётся полный доступ к определённому сервису, вместо привязки к конкретному функционалу. Например, в пиццериях есть заместители управляющего, которые могут вести определенные проекты: составлять графики или учитывать сырьё. Но у нас нет выдачи прав на конкретные компоненты системы. Приходится выдавать полный доступ к сервису, чтобы сотрудники могли получить доступ к составлению графиков или настройкам какого-либо компонента учёта.

Проблемы подтолкнули к тому, чтобы спроектировать и написать новую версию Auth. На старте проекта мы потратили 3 недели только на изучение стандартов авторизации и аутентификации OAuth 2.0 и OpenID Connect 1.0.

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

Что такое ОAuth2.0?


Разработку нового Auth мы решили начать с изучения доступных протоколов и технологий. Самый распространённый стандарт авторизации фреймворк авторизации OAuth2.0.

Стандарт был принят в 2012 году, и за 8 лет протокол меняли и дополняли. RFC стало настолько много, что авторы оригинального протокола решили написать OAuth 2.1, который объединит все текущие изменения по OAuth 2.0 в одном документе. Пока он на стадии черновика.

Актуальная версия OAuth описанна в RFC 6749. Именно его мы и разберем.

OAuth 2.0 это фреймворк авторизации.

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

Особенности:

  • Разделение сущности пользователя и приложения, запрашивающего доступ. Благодаря этому разделению мы можем управлять правами приложения отдельно от прав пользователя.

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

Разберёмся подробнее в особенностях.

Роли


В OAuth 2.0 определены четыре роли:

  • Resource owner сущность, которая имеет права доступа на защищённый ресурс. Сущность может быть конечным пользователем или какой-либо системой. Защищённый ресурс это HTTP endpoint, которым может быть что угодно: API endpoint, файл на CDN, web-сервис.
  • Resource server сервер, на котором хранится защищённый ресурс, к которому имеет доступ resource owner.
  • Client. Это приложение, которое запрашивает доступ к защищённому ресурсу от имени resource owner и с его разрешения с авторизацией.
  • Authorization server сервер, который выдаёт клиенту токен для доступа к защищённому ресурсу, после успешной авторизации resource owner.

Каждый участник взаимодействия может совмещать в себе несколько ролей. Например, клиент может быть одновременно resource owner, и запрашивать доступ к своим же ресурсам. Схему взаимодействия рассмотрим дальше.

Важно: клиент должен быть заранее зарегистрирован в сервисе. Как это сделать?

Регистрация клиента


Способ регистрации клиента, например, ручной или service discovery, вы выбираете сами, в зависимости от фантазии конкретной реализации. Но при любом способе при регистрации, кроме ID клиента, должны быть обязательно указаны 2 параметра: redirection URI и client type.

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

Client type тип клиента, от которого зависит способ взаимодействия с ним. Тип клиента определяется его возможностью безопасно хранить свои учётные данные для авторизации токен. Поэтому существует всего 2 типа клиентов:

  • Confidential клиент, который может безопасно хранить свои учётные данные. Например, к такому типу клиентов относят web-приложения, имеющие backend.
  • Public не может безопасно хранить свои учётные данные. Этот клиент работает на устройстве владельца ресурса, например, это браузерные или мобильные приложения.

Токены


Токен в OAuth 2.0 это строка, непрозрачная для клиента. Обычно строка выглядит как случайно сгенерированная её формат не имеет значения для клиента. Токен это ключ доступа к чему-либо, например, к защищённому ресурсу (access token) или к новому токену (refresh Token).

У каждого токена своё время жизни. Но у refresh token оно должно быть больше, т.к. он используется для получения access token. Например, если срок жизни access token около часа, то refresh token можно оставить жить на целую неделю.

Refresh token опционален и доступен только для confedential клиентов. Пользуясь опциональностью токена, в некоторых реализациях время жизни access token сделано очень большим, а refresh token вообще не используется, чтобы не заморачиваться с обновлением. Но это не безопасно. Если access token был скомпрометирован, его можно обнулить, а сервис получит новый Access token с помощью refresh token. В случае, если refresh token нет, то потребуется проходить процесс авторизации заново.

За access token закреплён определённый набор прав доступа, который выдаётся клиенту во время авторизации. Давайте разберёмся, как выглядят права доступа в OAuth 2.0.

Права доступа


Права доступа выдаются клиенту в виде scope. Scope это параметр, который состоит из разделённых пробелами строк scope-token.

Каждый из scope-token представляет определённые права, выдающиеся клиенту. Например, scope-token doc_read может предоставлять доступ на чтение к какому-то документу на resource server, а employee доступ к функционалу приложения только для работников фирмы. Итоговый scope может выглядеть так: email doc_read employee.

В OAuth 2.0 мы сами создаём scope-token, настраивая их под свои нужды. Имена scope-token ограничиваются только фантазией и двумя символами таблицы ASCII " и \.

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

Абстрактный OAuth 2.0. Flow c применением Access token


Мы рассмотрели роли, рассмотрели виды токенов, а также как выглядят scope. Посмотрим на flow предоставления доступа к сервису.

Ниже представлена абстрактная схема (или flow) взаимодействия между участниками. Все шаги на данной схеме выполняются строго сверху вниз. Разберём детальнее.



  • Client отправляет запрос на доступ к требуемому ресурсу resource owner.
  • Resource owner передаёт обратно клиенту authorization grant, который подтверждает личность resource owner и его права на ресурс, доступ к которому запрашивает client. В зависимости от flow это может быть токен или учётные данные.
  • Client отправляет authorization grant, полученный в предыдущем шаге authorization server, ожидая от него Access token для доступа к защищённому ресурсу.
  • authorization server убеждается в валидности authorization grant, после чего отсылает access token клиенту в ответ.
  • Получив access token, клиент запрашивает защищённый ресурс у resource server.
  • Resource server убеждается в корректности access token, после чего предоставляет доступ к защищённому ресурсу.

Клиент получает одобрение от resource owner, на основе которого ему выдаётся доступ к ресурсу. Всё просто. А будет ли так же просто, если мы добавим в эту схему работу с refresh token?

Абстрактный OAuth 2.0. Flow c применением Refresh token


Первый и второй шаги опущены из данной схемы они ничем не отличаются от схемы абстрактного flow выше.



Схема подробнее:

  • Client приходит c authorization grant к authorization server и просит предоставить ему access token и refresh token.
  • Authorization server убеждается, что с authorization grant всё нормально и возвращает клиенту запрошенные access token и refresh token.
  • Client с access token запрашивает защищённый ресурс, пока не получит первую ошибку доступа к ресурсу invalid token error.
  • После получения ошибки доступа, клиент идет к authorization server с refresh token и просит заменить просроченный access token на новый.
  • В ответ клиент получает новый access token, а также новый refresh token, либо продлевается время жизни старого refresh token.

Что такое grant?


Grant это данные, которые представляют из себя успешную авторизацию клиента владельцем ресурса, используемые клиентом для полученияaccess token.

Например, когда мы где-либо аутентифицируемся с помощью Google, перед глазами всплывает уведомление. В нём говорится, что такой-то сервис хочет получить доступ к данным о вас или к вашим ресурсам (выводятся запрашиваемые scope-token). Это уведомление называется Consent Screen.

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

Существует 4 + 1 способа получения grant grant type:

  • Authorization code используется для confedencial клиентов web-сервисов.
  • Client credentials используется для confedential клиентов, которые запрашивают доступ к своим ресурсам или ресурсам, заранее согласованным с сервером авторизации.
  • Implicit использовался public-клиентами, которые умеют работать с redirection URI (например, для браузерных и мобильных приложений), но был вытеснен authorization code grant с PKCE (Proof Key for Code Exchange дополнительная проверка, позволяющая убедиться, что token получит тот же сервис, что его и запрашивал. Прочитать подробнее RFC 7636).
  • Resource owner password credentials. В RFC 6819, посвящённому безопасности в OAuth 2.0, данный тип grant считается ненадёжным. Если раньше его разрешалось использовать только для миграции сервисов на OAuth 2.0, то в данный момент его не разрешено использовать совсем.
  • Device authorization (добавлен в RFC 8628) используется для авторизации устройств, которые могут не иметь веб-браузеров, но могут работать через интернет. Например, это консольные приложения, умные устройства или Smart TV.

Актуальными можно считать только authorization code (с PKCE), client credentials и device authorization grant, но мы рассмотрим все. Рассматривать grant будем в порядке возрастания сложности понимания.

Client credentials grant flow


Имеет самый простой flow, напоминающий обычную авторизацию на любом сервисе. Она выполняется с помощью учётных данных клиента, которые представляют собой client id и client secret аналог логина и пароля для пользователя. Так как для аутентификации требуется client secret, который должен соответствующе храниться, данный flow могут использовать только confedential клиенты.



Схема проста: клиент аутентифицируется на сервере авторизации передавая client id и client secret. В ответ получает access token, с которым уже может получить доступ к нужному сервису.

Этот flow требуется, когда клиент пытается получить доступ к своим ресурсам или ресурсам, заранее согласованным с сервером авторизации. Например, сервису А нужно время от времени ходить в сервис Б и актуализировать там данные о количестве пиццерий в сети.

Resource owner password credentials flow


По текущим рекомендациям безопасности описанных в данном RFC, данный flow не рекомендуется использовать вовсе из-за явных проблем с безопасностью.



Resource owner передаёт свой логин и пароль клиенту, например, через формы на клиенте. Клиент, в свою очередь, с помощью него получает access token (и, опционально, refresh token).

Здесь есть проблема. Resource owner просто берёт и отдаёт в открытом виде свой логин и пароль клиенту, что не безопасно. Изначально он был сделан только для клиентов, которым вы доверяете или тех, что являются частью операционной системы. Позже он был разрешён только для миграции с аутентификации по логину и паролю на OAuth 2.0. Текущие рекомендации по безопасности запрещают его использование.

Authorization code


Самый распространённый flow на данный момент. В основном используется для confidential клиентов, но с появлением дополнительной проверки с помощью PKCE, может применяться и для public-клиентов.

В данном flow взаимодействие client с resource owner проходит через user-agent (браузер). К user-agent есть одно требование: он должен уметь работать с HTTP-редиректами. Без этого resource owner не сможет попасть к серверу авторизации и вернуться обратно с grant.



Данный flow сложнее, чем предыдущие, поэтому будем разбирать по шагам. Для начала представим, что мы resource owner и перешли на страницу сервиса онлайн-обучения, который хочет сохранять результаты обучения к нам в облако. Ему требуется получить доступ к нашему ресурсу, например, определённой директории в облаке. Мы нажимаем на Авторизоваться и начинается путешествие по Authorization code grant flow:

  • На первом шаге клиент перенаправляет resource owner с помощью user-agent на страницу аутентификации Authorization server. В URI он указывает client ID и redirection URI. Redirection URI используется для понимания, куда вернуть resource owner после того, как авторизация пройдёт успешно (resource owner выдаст разрешение на scope, запрашиваемый клиентом).
  • Взаимодействуя с сервером авторизации через user-agent, resource owner проходит аутентификацию на сервере авторизации.
  • Resource owner проверяет права, которые запрашивает клиент на consent screen и разрешает их выдачу.
  • Resource owner возвращается клиенту с помощью user-agent обратно на URI, который был указан как redirection URI. В качестве query-параметра будет добавлен authorization code строка, подтверждающая то, что resource owner выдал необходимые права сервису.
  • С этим authorization code клиент отправляется на сервер авторизации, чтобы получить в ответ access token (ну и refresh token, если требуется).
  • Сервер авторизации валидирует authorization code, убеждаясь, что токен корректный и выдаёт клиенту access token (и опционально refresh token). С его помощью клиент сможет получить доступ к заветному ресурсу.

Если представить нас на месте resource owner, то мы видим просто перенаправление на сервер авторизации, аутентифицируемся, подтверждаем доступ на Consent screen и нас отправляет на уже работающий сервис. Например, мы проходим это много раз, когда заходим на сервис под учётной записью Google, Facebook или Apple.

Следующий flow построен на основе этого.

Implicit grant


Это оптимизация Authorization code grant flow для public-клиентов, которые умеют работать с redirection URI. Например, для браузерных приложений на JavaScript, или мобильных приложений. Требование к user-agent, с помощью которого взаимодействуют клиент и resource owner, сохраняется: он должен уметь работать с HTTP-редиректами.

Между authorization code и implicit есть основное отличие: вместо получения authorization code и access token по нему, мы сразу получаем access token после успешной авторизации resource owner. Кроме того, здесь не используется client secret из соображений безопасности приложение можно дизассемблировать и получить его. Подлинность проверяется только по redirection URI.



Многие шаги из данной схемы похожи на шаги из authorization code, но предлагаю их разобрать также подробно. Представим, что некое браузерное приложение хочет сохранять свои настройки в нашем Git-репозитории. Мы нажимаете Войти в GitHub и на этом этапе начинается работа Implicit flow:

  • Клиент с помощью user-agent и HTTP-редиректа перенаправляет resource owner на сервер авторизации. В параметрах запроса передает client ID и redirection URI, которые нужны для аутентификации клиента и последующего возврата resource owner обратно.
  • Resourse owner аутентифицируется, взаимодействуя через user-agent с сервером авторизации. Заодно подтверждает выдачу grant клиенту, с client ID которого он пришёл.
  • После подтверждения выдачи grant (нажатия allow на consent screen), user-agent возвращает resource owner на redirection URI. Кроме того, в URI fragment передаётся access token (URI fragment это то, что обычно идёт в URI после символа #).
  • Сам фрагмент сохраняется локально в user-agent. User-agent двигается дальше по redirection URI за web-страницей, которая нужна для получения access token и других необходимых данных из фрагмента. Она может находиться как на самом клиенте, так и на удалённом ресурсе, например, на CDN.
  • Web-ресурс возвращает web-страницу (может содержать в себе скрипт), которая может прочитать полностью redirection URI, в том числе и значение, указанное в фрагменте.
  • User-agent отрисовывает локально полученную страницу, включая исполнение скриптов, которые он получил от web-hosted client resource, которые получают access token.
  • Полученный access token user-agent просто передаёт клиенту.

Это сложный flow. Он мало используется в реальных сценариях. Но его всё ещё можно встретить в legacy-проектах.

Device authorization (RFC 8628)


С 2012 до 2019 появилось много умных устройств, на которых неудобно авторизоваться. Например, неудобно вводить сложный логин и пароль на телевизоре каждый раз при открытии ресурса. На некоторых устройствах это невозможно, например на серверных ОС без графического интерфейса. В августе 2019 этот flow появился как раз для таких сценариев.

Есть, как минимум, 3 требования к устройствам, чтобы работа с помощью Device authoraztion grant flow была возможна:

  • Устройство должно иметь возможность совершать исходящие HTTPS-запросы.
  • Устройство должно иметь возможность отображать URI и код пользователю.
  • Каждое авторизуемое устройство принадлежит resource owner, который для успешной авторизации должен иметь другое устройство с браузером, чтобы перейти по указанному URI и ввести указанный код.



Возможно, схема кажется сложной из-за обилия стрелок. Разберём её также пошагово, как и разбирали сложные flow до него.

Представим, что мы пытаемся авторизоваться на web-сервисе с помощью телевизора. Мы видим кнопку Авторизоваться как устройство и нажимаем. В этот момент начинается наш Device flow:

  • Телевизор делает запрос на сервер авторизации, передавая ему свой client ID.
  • Сервер авторизации убеждается, что такой клиент зарегистрирован и имеет соответствующий тип grant.
  • Если всё хорошо, то Authorization server возвращает device code, user code и verification URI. Device code это уникальный идентификатор устройства, которое авторизуется в системе.
  • Устройство отображает user code и verification URI владельцу этого устройства resource owner. Redirection URI может быть передан как строкой, так и с помощью QR-кода ограничений нет.
  • После того, как устройство отобразило user code и verification URI, оно начинает раз в некоторое время опрашивать сервер авторизации о её успешности.
  • Дальше в бой вступает resource owner. Он переходит по указанному verification URI, аутентифицируется и вводит user code, который он получил от устройства, подтверждая выдачу необходимых scope устройству. На этом действия от имени resource owner закончены.
  • Всё это время устройство (пункт 3) опрашивало сервер авторизации о её успешности. Устройство в очередной раз идёт к серверу авторизации со своим device code и client ID в надежде, что авторизация на этот раз прошла.
  • В этот раз, когда resource owner подтвердил передачу необходимых прав устройству, сервер авторизации возвращает в ответе на запрос access token (если предусмотрено настройками сервера и refresh token). И с помощью токена устройство уже может продолжать работу с ресурсом.

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

Вместо вывода


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

Если хотите погрузиться в тематику детальнее, то рекомендую в RFC 6749 (для OAuth 2.0) и RFC 8628 (для Device Flow). Кроме того, следить за актуальными версиями RFC можно на ресурсе, посвящённому OAuth.

Если статья была полезна и захотите подробностей пишите в комментариях, и в следующих статьях расскажу о PKCE, о протоколе аутентификации OpenID Connect 1.0, о нашей реализации сервера аутентификации и многом другом.

Полезные ссылки:

Подробнее..

Best practices для клиент-серверного проекта PoC

22.12.2020 12:11:10 | Автор: admin
image
Типичный проект PoC (Proof of Concept) состоит из клиента с GUI, сервера c бизнес логикой и API между ними. Также используется база данных, хранящая оперативную информацию и данные пользователей. Во многих случаях необходима связь с внешними системами со своим API.
Когда у меня возникла необходимость в создании проекта PoC, и я начал разбираться в деталях, то оказалось, что порог вхождения в веб-программирование весьма высок. В крупных проектах для каждого компонента есть выделенные специалисты: front-end, back-end разработчики, UX/UI дизайнеры, архитекторы баз данных, специалисты по API и информационной безопасности, системные администраторы. В небольшом PoC надо самому во всем разобраться, выбрать подходящее техническое решение, реализовать и развернуть. Ситуацию ухудшает тот факт, что из обучающих материалов не всегда понятно, почему предлагается сделать именно так, а не иначе, есть ли альтернативы, является ли решение best practice или это частное мнение автора. Поэтому я разработал заготовку под названием Common Test DB, отвечающую лучшим практикам. Ее можно использовать для начала любого проекта, остается только наполнить функциональным смыслом.
В статье я подробно опишу примененные best practices, расскажу про имеющиеся альтернативы и в конце размещу ссылки на исходники и работающий в сети пример.

Требования к проекту PoC


Начнем с минимальных требований к проекту PoC. В статье Блокчейн: что нам стоит PoC построить? я уже рассматривал из каких частей состоит универсальная информационная система. В этой статье подробно разберем состав типичного проекта PoC. Даже минимальный проект в клиент-серверной архитектуре требует значительной функциональности и технической проработки:

Клиент:
  • Должен осуществлять запросы к серверу, используя REST API
  • Регистрировать, авторизовать, аутентифицировать пользователей
  • Принимать, разбирать и визуализировать пришедшие данные

Сервер
  • Должен принимать HTTP/HTTPS запросы от клиента
  • Осуществлять взаимодействие с внешними системами
  • Взаимодействовать с базой данных (реализовать базовую функциональности CRUD)
  • Обеспечивать безопасность данных, вызовов API и конфигурационных параметров
  • Осуществлять логирование и мониторинг работоспособности

База данных
  • Необходимо реализовать структуру DB
  • Наполнить DB начальными значениями

Архитектура
  • Проект PoC должен быть реализован с возможностью расширения функциональности
  • Архитектура должна предусматривать масштабирование решения

Технологический стек


  • В настоящее время для большинства веб проектов используетсяязык JavaScript.Над JavaScript есть надстройкаTypeScript, которая обеспечивает типизацию переменных и реализацию шаблонов.
  • Для полнофункциональных веб проектов есть несколько стандартных стеков технологий. Один из самых популярных: MEVN (MongoDB + Express.js + Vue.js + Node.js), мне больше нравится PEVN (PostgreSQL + Express.js + Vue.js + Node.js), т.к. RDB базы мне технологически ближе, чем NoSQL.
  • Для GUI существует несколькофреймворков (Vue, React, Angular). Тут выбор, скорее, определяется традициями или личными предпочтениями, поэтому сложно говорить, что лучше, а что хуже. Сначала я выбрал Vue.js, да так на нем и остался. Этот фреймворк хорошо развивается, для него есть готовыевизуальныекомпоненты встилеMaterial Design (Vuetify), которые хорошо смотрятся даже при непрофессиональном применении. Считается, что для React порог вхождения выше, чем для Vue. В React сначала надо осознать специфичную объектную модель, которая отличается от классического веб программирования: компоненты как функции, компоненты как классы, цепочки вызовов.
  • Выбор базы данных уже сложнее, чем личные предпочтения. Но для PoC, зачастую, требуется не функциональность или производительность, а простота. Поэтому в примере будем использовать самую простую в мире базу SQLite. В промышленных PoC в качестве SQL базы я использую PostgreSQL, в качестве NoSQL ее же, т.к. запас прочности уPostgreSQL огромен. Прочность, конечно, не бесконечна и когда-нибудь настанет необходимость перехода на специализированную базу, но это отдельная тема для обсуждения.

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

Сервер


HTTP сервер


В качестве HTTP сервера япробовал два варианта:

Express более зрелый, Fastify, говорят, более быстрый. Есть еще варианты серверов, примеры перечислены в статье The Best 10 Node.js Frameworks. Я использовал самый популярный Express.js.

HTTPS запросы


Говоря про HTTP, я всегда подразумеваю HTTPS, т.к. без него крайне сложно построить безопасное взаимодействие между клиентом и сервером. Для поддержки HTTPS надо:
  • Получить SSL (TLS) сертификат
  • На сервере реализовать поддержку HTTPS протокола
  • На клиенте использовать запросы к клиенту с префиксом HTTPS

Получаем сертификат
Для организации работы HTTPS протокола нужен SSL сертификат. Существуют бесплатные сертификаты Lets Encrypt, но солидные сайты получают платные сертификаты от доверительных центров сертификации CA(Certificate Authority). Для наших тестовых целей на локальном хосте достаточно, так называемого, self-signedcertificate. Он генерируется с помощью утилиты openssl:
openssl req -nodes -new -x509 -keyout server.key -out server.crt

Далее отвечаем на несколько вопросов про страну, город, имя, email и т.п. В результате получаемдва файла:
  • server.key ключ
  • server.crt сертификат

Их нужно будет подложить нашему Express серверу.

HTTPS сервер
С Express сервером все достаточно просто, можно взять стандартный пакет HTTPS из Node.js, и использовать официальную инструкцию: How to create an https server?
После реализации HTTPS сервера используем полученные файлы server.key и server.crt и стандартный для HTTPS порт443.

Взаимодействие с внешними системами


Самая известная библиотека для реализации HTTP/HTTPS запросов: Axios. Она используется как в сервере для вызова API внешних систем, так и в клиенте для вызова API сервера. Есть еще варианты библиотек, которые можно использовать дляспецифичных целей: Обзор пяти HTTP-библиотек для веб-разработки.

Взаимодействие с базой данных


Для работы с базой данных я использовал самую популярную библиотеку для Node.js: Sequelize. В ней мне больше всего нравится то, что переключиться между различными типами баз данных можно всего лишь изменив несколько настроечных параметров. При условии, конечно, что в самом коде не используется специфика определенной базы.Например, чтобы переключиться с SQLight на PostgreSQL, надо в конфигурационном файле сервера заменить:
dialect: 'sqlite'

на
dialect: 'postgres'

И при необходимости изменить имя базы, пользователя и хост.
В PoC я использовал базу данныхSQLite, которая не требует установки.Для администрирования баз данных есть хорошо развитые GUI:

Безопасность доступа к данным


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

  • Получить данные могут все зарегистрированные пользователи.
  • Изменить и удалить данные может только владелец этих данных.

Такая модель доступа к данным называетсяDiscretionary Access Control (DAC), по-русски: избирательное управление доступом. В этой модели владелец данных может делать с ними любые CRUD операции, а права остальных пользователей ограниченны.
Существуют несколько моделей контроля доступа к данным. ПомимоDiscretionary Access Control (DAC)OWASP рекомендует (Access Control Cheat Sheet) использовать следующие:
  • Role-Based Access Control (RBAC)
  • Mandatory Access Control (MAC)
  • Permission Based Access Control

Безопасность вызовов API


В моей предыдущей статье я рассматривал все возможные угрозы: Безопасность REST API от А до ПИ.В PoC реализованы следующие механизмы:
  • Cross-Site Request Forgery (CSRF) (Межсайтовая подмена запросов)

От данной угрозы реализован механизм CSRF токенов когда для каждой сессии пользователя генерируется новый токен (он же SessionId) и сервер проверяет его валидность при любых запросах с клиента. Алгоритм генерации и проверки SessionId подробно описан далее в разделе Авторизация, аутентификация пользователей.
  • Cross-origin resource sharing (CORS) (Кросс-доменное использование ресурсов)

Защита реализована с помощью пакета CORS. В config файле сервера указываются адрес (origin), с которого могут поступать API запросы и список методов (methods), которые может использовать клиент. В дальнейшем сервер будет автоматически ограничивать прием запросов в соответствии с этими настройками.
Для защиты от угроз, перечисленных далее, я использовал пакетhelmet, который позволяет задать значения для определенных HTTP заголовков:
  • Cross-site Scripting (XSS) (Межсайтовое выполнение скриптов)

Для защиты выставляется заголовок, ограничивающий выполнение скриптов:
X-XSS-Protection: 1; mode=block

  • Insecure HTTP Headers

Блокируется отправка сервером заголовка, дающего дополнительную информацию злоумышленнику:
X-Powered-By: Express

  • Insecure HTTP Headers: HTTP Strict Transport Security (HSTS)

Используется Strict-Transport-Security заголовок, который запрещает браузеру обращаться к ресурсам по HTTP протоколу, только HTTPS:
Strict-Transport-Security: max-age=15552000; includeSubDomains

  • Insecure HTTP Headers: X-Frame-Options (защита от Clickjacking)

Данный заголовок позволяет защититься от атаки Clickjacking. Он разрешает использовать фреймы только в нашем домене:
X-Frame-Options: SAMEORIGIN

  • Insecure HTTP Headers: X-Content-Type-Options

Установка заголовка X-Content-Type-Options запрещает браузеру самому интерпретировать тип присланных файлов и принуждает использовать только тот, что был прислан в заголовке Content-Type:
X-Content-Type-Options: nosniff

Защита от DoS


Необходимо защитить сервер и от отказа в обслуживании (DoS-атаки). Например, ограничить число запросов от одного пользователя или по одному ресурсу в течении определенного времени.
Для Node.js существуют средства, позволяющие автоматически реализовывать ограничения на число запросови сразу посылать ответ 429 Too Many Requests, не нагружая бизнес логику сервера.
В примере реализовано простейшее ограничение на число запросов от каждого пользователя в течении определенного времени по таблице Data. В промышленных системах надо защищать все запросы, т.к. даже один запрос, оставшийся без проверки может дать возможность провести атаку.

Безопасность конфигурационных параметров


Настройки для клиента и сервера хранятся в файлах configв JSON формате. Чувствительные настройки сервера нельзя хранить вconfig файлах, т.к. они могут стать доступны в системах версионного контроля. Для такой информации как: логины/пароли для базы данных, ключи доступа к API и т.д. надо использовать специализированные механизмы:
  • Задавать эти параметры в .env файле. Файл .env прописан в gitignore и не сохраняется в системах версионного контроля, поэтомупоэтому туда можно безопасно записывать чувствительную информацию.
  • Использовать механизм переменных окружения (environment variables), которые устанавливаются вручную или прописываютсяинсталляторами.

Логирование


Стандартные требования к логированию:
  • Обеспечить разный уровень логирования (Debug; Info; Warning; Error и т.д.)
  • Логировать HTTP/HTTPS запросы
  • Дублировать консольный вывод в файлы на диске
  • Обеспечить самоочистку файлов логирования по времени иобъёму

Варианты пакетов для логирования, которые я пробовал:

В проекте я использовал log4js. Пакет log4jsработает интуитивно понятно и достаточно развит, чтобы реализовать все требования.

Мониторинг


Мы не будем останавливаться на внешних система мониторинга: Обзор систем мониторинга серверов, т.к. они начинают играть важную роль не на этапе PoC, а в промышленной эксплуатации.
Обратимся к внутренним системам, которые позволяют наглядно посмотреть, что сейчас происходит с сервером, например пакетexpress-status-monitor. Если его установить, топо endpoint
/monitor

можно наглядно мониторить простейшие параметры работы сервера, такие как загрузка CPU, потребление памяти, http нагрузку и т.п. Приведу скриншот из нашего примера. На скриншоте горит красная плашка Failed и может показаться, что что-то не так. Но, на самом деле, все в порядке, т.к. вызов API сознательно делается на несуществующий endpoint:
/wrongRoutePath

image

База данных


Структурабазы данных


В нашем примере реализованы две таблицы с говорящими именами:
  • Users
  • Data

Лучшие практики говорят, что все проверки надо помаксимуму отдавать базе данных. Поэтому в схеме базы данных аккуратно определяем все типы данных, добавляем ограничения (constraints) и внешние ключи.Для пакета sequelize данная функциональность подробно описана в стандартной документации.
В нашем пример сделан один внешний ключ UserId в таблице Data для того, чтобы гарантировать, что у каждой записи в таблицеData будет определенный владелец User. Это позволит при изменении или удалении записи в Data реализовать проверку пользователя, т.к. по нашей логике данные может удалять только их владелец.
Еще одна особенность нашей схемы в том, что один столбец таблицы Data задан в формате JSON. Такой формат удобно использовать, если внешняя система возвращает данные в JSON. В этом случае можно просто записать полученные данные, не тратя усилия на парсинг,а потом делать SQL запросы, используя расширенный синтаксис.Описание работы со столбцами JSON можно найти в официальной документации баз данных. В качестве независимого описания мне понравилась статья на Хабре, в которой описаны все варианты запросов: "JSONB запросы в PostgreSQL.

Формальная схема DB
Cхема таблицыUsers:
  • id INTEGERPRIMARY KEY AUTOINCREMENT уникальный ID
  • uuid UUID NOT NULL UNIQUE уникальный UUID, по нему идет связь с данными этого пользователя в таблице Data
  • email VARCHAR(255) NOT NULL UNIQUE
  • password VARCHAR(64)
  • ddosFirstRequest DATETIME
  • ddosLastRequest DATETIME
  • ddosRequestsNumber DECIMAL
  • lastLogin DATETIME
  • loginState VARCHAR(255) NOT NULL
  • sessionId VARCHAR(1024)
  • commonToken VARCHAR(1024)
  • googleToken VARCHAR(1024)
  • googleAccessToken VARCHAR(1024)
  • googleRefreshToken VARCHAR(1024)
  • createdAt DATETIME NOT NULL
  • updatedAt DATETIME NOT NULL

Схема таблицыData:
  • id INTEGERPRIMARY KEY AUTOINCREMENT
  • uuid UUID NOT NULL UNIQUE
  • ownerUuid UUID NOT NULL
  • data JSON NOT NULL
  • UserId INTEGER REFERENCES Users (id) ON DELETE CASCADE ON UPDATE CASCADE,
  • createdAt DATETIME NOT NULL
  • updatedAt DATETIME NOT NULL


Начальные значения


  • В таблице Users задан один пользовательuser@example.comс паролем password.
  • В таблице Data записано несколько предопределенных значений, которые можно получить из GUI.

Далее перейдем к клиенту.

Клиент


HTTPS запросы


Для безопасной передачи информации клиент использует HTTPS протокол. Для запросов используется та же библиотека, что и на сервере для вызова API внешних систем: Axios.

RESTful API


REST API лучше реализовывать, используя стандартные средства, чтобы было проще тестировать и документировать, например SwaggerHub
Наше REST API сформировано по правилам Open API и использует стандартные HTTP методы для манипуляции объектами из таблиц Users и Data.В схеме нашего API не все сделано по чистым правилам REST, но главное соблюдена общая концепция.
Хорошей практикой являетсяограничение объёма возвращаемых данных. Для этого используются параметры в запросе: фильтр (filter), ограничение (limit) и смещение (offset). В примере этого нет, но в промышленных системах эта функциональность должна быть реализована.Но даже если реализованы ограничивающие механизмы на клиенте, должна осуществляться дополнительная проверка на сервере на максимальные значения. В нашем примере в конфигурации сервера реализован ограничитель на максимальное число возвращаемых строк из базы,который подставляется вSELECT запросы:limit: 1000

YAML файл с Open API описанием
---swagger: "2.0"info:  description: "This is a common-test-db API. You can find out more about common-test-db\    \ at \nhttps://github.com/AlexeySushkov/common-test-db\n"  version: "2.0.0"  title: "common-test-db"  contact:    email: "alexey.p.sushkov@gmail.com"  license:    name: "MIT License"    url: "https://github.com/AlexeySushkov/common-test-db/blob/main/LICENSE"host: "localhost:443"basePath: "/commontest/v1"tags:- name: "data"  description: "Everything about Data"  externalDocs:    description: "Find out more"    url: "https://github.com/AlexeySushkov/common-test-db/"- name: "users"  description: "Everything about Users"  externalDocs:    description: "Find out more"    url: "https://github.com/AlexeySushkov/common-test-db/"schemes:- "https"paths:  /data:    get:      tags:      - "data"      summary: "Gets all data"      description: "Gets all data"      operationId: "getData"      produces:      - "application/json"      parameters: []      responses:        "200":          description: "successful operation"          schema:            type: "array"            items:              $ref: "#/definitions/Data"        "400":          description: "Invalid status value"      x-swagger-router-controller: "Data"    post:      tags:      - "data"      summary: "Add a new data to the db"      operationId: "addData"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "Data object that needs to be added to the db"        required: true        schema:          $ref: "#/definitions/Data"      responses:        "500":          description: "Create data error"      x-swagger-router-controller: "Data"    put:      tags:      - "data"      summary: "Update an existing data"      operationId: "updateData"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "Data information that needs to be changed"        required: true        schema:          $ref: "#/definitions/Data"      responses:        "400":          description: "Invalid uuid"        "404":          description: "User not found"        "500":          description: "Update data error"      x-swagger-router-controller: "Data"    delete:      tags:      - "data"      summary: "Delete an existing data"      operationId: "deleteData"      consumes:      - "application/x-www-form-urlencoded"      - "application/json"      produces:      - "application/json"      parameters:      - name: "uuid"        in: "formData"        description: "uuid"        required: true        type: "string"      responses:        "400":          description: "Invalid uuid"        "404":          description: "User not found"        "500":          description: "Delete data error"      x-swagger-router-controller: "Data"  /users:    post:      tags:      - "users"      summary: "Register new user"      operationId: "userRegister"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "User that needs to be deleted"        required: true        schema:          $ref: "#/definitions/User"      responses:        "500":          description: "Register user error"      x-swagger-router-controller: "Users"    put:      tags:      - "users"      summary: "Update existing user"      operationId: "userUpdate"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "User information that needs to be changed"        required: true        schema:          $ref: "#/definitions/User"      responses:        "404":          description: "User not found"        "500":          description: "Delete user error"      x-swagger-router-controller: "Users"    delete:      tags:      - "users"      summary: "Delete user"      operationId: "userDelete"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "User that needs to be added to the db"        required: true        schema:          $ref: "#/definitions/User"      responses:        "404":          description: "User not found"        "500":          description: "Delete user error"      x-swagger-router-controller: "Users"  /users/login:    post:      tags:      - "users"      summary: "Login"      operationId: "userLogin"      consumes:      - "application/json"      produces:      - "application/json"      parameters:      - in: "body"        name: "body"        description: "User that needs to be added to the db"        required: true        schema:          $ref: "#/definitions/User"      responses:        "404":          description: "User not found"        "500":          description: "Login user error"      x-swagger-router-controller: "Users"definitions:  Data:    type: "object"    required:    - "Counter1"    - "Counter2"    properties:      Counter1:        type: "integer"        format: "int64"      Counter2:        type: "integer"        format: "int64"    example:      Counter1: 10      Counter2: 20  User:    type: "object"    required:    - "email"    - "password"    properties:      email:        type: "string"      password:        type: "string"    example:      password: "password"      email: "email"  ApiResponse:    type: "object"    properties:      status:        type: "string"


Интересная возможность встроить Swagger прямо в сервер, далее подложить ему YAML, реализующий API и по endpoint:
/api-docs

получить стандартную Swagger панель управления и осуществлять тестирование и просмотр документации. Скриншот Swagger, встроенного в наш сервер:

image

GraphQL


Существуют и альтернативы REST API, например GraphQL. Данный стандарт разработан Facebook и позволяет избавиться от недостатков REST:
  • В REST API взаимодействие происходит с использованием многочисленные endpoints. В GraphQL одна.
  • В REST API для получения данных от разных endpoints необходимо осуществить множественные запросы. В GraphQL все необходимые данные можно получить одним запросом.
  • В REST API клиент не может контролировать объём и содержимое данных в ответе. В GraphQL набор возвращаемых данных предсказуем, т.к. задается в запросе. Это сразу избавляет от угрозы: Excessive Data Exposure (Разглашение конфиденциальных данных)
  • В REST API отсутствует обязательность формальной схемы. В GpaphQL создание схемы API с определенными типами данных обязательно, при этом автоматически получается документация и сервер для тестирования (самодокументироваемость и самотестируемость).
  • Клиент всегда может запросить схему API с сервера, тем самым синхронизировать формат своих запросов
  • Из коробки работают подписки, реализованные на технологии WebSockets

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

Регистрация,аутентификация и авторизация пользователей


Если приложение доступно из Интернета, то в форме регистрации имеет смысл использовать функциональность reCaptсha от Google для защиты от ботов и DoS атак. В нашем примере я использую пакет vue-recaptcha.
Для использованияreCaptсha нужно на сайте Google зарегистрировать приложение, получить sitekey и прописать его в настройках клиента. После этого на форме появится, известный всем, вопрос про робота:
image

В примере реализована регистрация пользователей с помощью логина/пароля ис использованием Google Account.
  • При регистрации по логин/пароль пароли в базе данных, разумеется, не хранятся в виде текста, а хранится только хеш. Для его генерации используется библиотекаbcrypt, которая реализует алгоритм хеширванияBlowfish. Сейчас уже есть мнение, что надо использовать более надежные алгоритмы: Password Hashing: Scrypt, Bcrypt and ARGON2.
  • При регистрации с помощью Google Account организация хранения аутентификационных данных проще, т.к. в базу записывается только token, полученный от Google.

Для реализации различных схем аутентификации есть библиотека Passport, которая упрощает работу и скрывает детали. Но чтобы понять как работают алгоритмы, в примере все сделано в соответствии со стандартами, которые я уже описывал в своей статье: Современные стандарты идентификации: OAuth 2.0, OpenID Connect, WebAuthn.
Далее в статье разберем практическую реализацию.

Аутентификация по логину/паролю (Basic Authentication)
Тут все достаточно просто. При получении запроса на логин сервер проверяет, что пользователь с присланнымemail существует и что хеш полученного пароля совпадает с хешом из базы. Далее:
  • Сервер формирует SessionId в формате UUID и Token в формате JWT (JSON Web Token)
  • Токену присваиватся время жизни, которое определяется бизнес задачами. Например, Google устанавливет своим токенам время жизни 1 час.
  • Для формирования токена используется пакетjsonwebtoken
  • В JWT помещается email пользователя, который подписывается ключом сервера. Ключ это текстовая строка, которая хранится, как конфигурационный параметр на сервере.Содержимое токена кодируется Base64
  • SessionId сохраняется в базе и отправляется клиенту в заголовке X-CSRF-Token
  • Token сохраняется в базе и отправляется клиенту в теле HTTP ответа
  • Получив SessionId клиент сохраняетSessionId в sessionStorageбраузера
  • Token записывается в localStorage или Сookiesбраузерадля реализации возможности запомнить меня на этом устройстве
  • В дальнейшем клиент будет присылать SessionId в HTTP заголовкеX-CSRF-Token:

X-CSRF-Token: 69e530cf-b641-445e-9866-b23c492ddbab

  • Token будет присылаться в заголовкеHTTP Authorization с префиксомBearer:

Authorization: BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE2MDYxNTY3MTUsImV4cCI6MTYwNjc2MTUxNX0.h3br5wRYUhKIFs3SMN2ZPvMcwBxKn7GMIjJDzCLm_Bw

  • Сервер каждый раз проверяетSessionId на то, что сессия существует и связана с пользователем. Таким образом, гарантируется, что запрос приходит от залогиненного клиента, а не стороннего сайта злоумышленника.
  • Сервер при каждом запросе будет проверять присланныйToken на валидность:

  1. Токен подписан сервером;
  2. Пользователь с email существует;
  3. Время жизни токена не истекло. Если же оно истекло, то при выполнении предыдущих условий автоматически формируется новый токен.

В общем случае излишне генерировать и SessionId и Token. В нашем PoC для примера реализованы оба механизма. Посмотрим внимательней на наш JWT. Пример закодированного JWT токена из заголовка:
Authorization:yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE2MDYxNTY3MTUsImV4cCI6MTYwNjc2MTUxNX0.h3br5wRYUhKIFs3SMN2ZPvMcwBxKn7GMIjJDzCLm_Bw

Раскодируем его открытыми средствами. Например самый простой способ загрузить на сайтhttps://jwt.io

image

Мы видим, что информация, помещенная вJWT не зашифрована, а только подписана ключом сервера и туда нельзя помещать чувствительные к разглашению данные!

Аутентификация с помощью Google Account (Token-Based Authentication)
Тут посложнее, поэтому нарисую диаграмму:

image

1. Пользователь заходит на GUI PoC, выбирает Login with Google".
2. Клиент запрашивает у сервера SessionId и настройки Google. Настройки надо предварительно получить с сайта Google, например, по инструкции из моей статьи: Современные стандарты идентификации: OAuth 2.0, OpenID Connect, WebAuthn
3. Клиент сохраняет SessionId в sessionStorageбраузера
4. Из браузера формируется GET запрос на GoogleAuthorization Server (красная стрелка). Для понимания алгоритма, надо обратить внимание, что ответ на этот запрос браузер получит от Google только в самом конце call flow на шаге 13 и это будет 307 (Redirect).

Формат GET запроса:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=918876437901-g312pqdhg5bcju4hpr3efknv.apps.googleusercontent.com&scope=openid+email&redirect_uri=http://localhost:8081/redirect&access_type=offline&state=450c2fd9-0a5e-47d2-8ed5-dd1ff4670b58

В этом запросе:
  • accounts.google.com/o/oauth2/v2/auth endpoint для начала аутентификации. У Google есть, кстати, адрес, по которому можно посмотреть актуальный список всех Google API endpoints:https://accounts.google.com/.well-known/openid-configuration
  • response_type=code параметр говорит, что ожидаем получить в ответAuthorization Code
  • client_id Client ID, выданный при регистрации приложения на Google (в примере указан не настоящий)
  • scope=openid email к каким данным пользователя мы хотим получить доступ
  • redirect_uri = localhost:8081/redirectCallback адрес, заданный при регистрации приложения. В нашем случае это адрес на нашем сервере, который получит запрос от Google
  • state = SessionId, который передается между клиентом, Google и сервером для защиты от вмешательства внешнего злоумышленника

5. Google Authorization Server показывает стандартную Google форму логина.
6. Пользователь вводит свои логин/пароль от Google аккаунта.
7. Google проверяет пользователя и делает GET запрос на адрес Callback с результатом аутентификации, Authorization Code в параметре code и нашем SessionId в параметреstate:
  • state: '450c2fd9-0a5e-47d2-8ed5-dd1ff4670b58',
  • code: '4/0AY0e-g4pg_vSL1PwUWhfDYj3gpiVPUg20qMkTY93JYhmrjttedYwbH376D_BvzZGmjFdmQ',
  • scope: 'email openid www.googleapis.com/auth/userinfo.email',
  • authuser: '1',
  • prompt: 'consent'

8. Сервер, не отправляя пока ответ на GET, формирует POST запрос с Authorization Code, Client ID и Client Secret:
POSThttps://oauth2.googleapis.com/token?code=4/0AY0e-g4pg_vSL1PwUWhfDYj3gpiVPUg20qMkTY93JYhmrjttedYwbH376D_BvzZGmjFdmQ&client_id=918876437901-g312pqdhg5bcju4hpr3efknv.apps.googleusercontent.com&client_secret=SUmydv3-7ZDTIh8аНК85chTOt&grant_type=authorization_code&redirect_uri=http://localhost:8081/redirect

  • oauth2.googleapis.com/token это endpoint для получения token
  • code только что присланный code
  • client_id Client ID, выданный при регистрации приложения на Google (в примере указан не настоящий)
  • client_secret Client Secret, выданный при регистрацииприложения на Google (в примере указан не настоящий)
  • grant_type=authorization_code единственно возможное значение из стандарта

9. Google проверяет присланные данные и формирует access token в формате JWT (JSON Web Token), подписанный своим приватным ключом. В этом же JWT может содержаться и refresh token, c помощью которого возможно продолжение сессии после ее окончания:
  • access_token: 'ya29.a0AfH6SMBH70l6wUe1i_UKfjJ6JCudA_PsIIKXroYvzm_xZjQrCK-7PUPC_U-3sV06g9q7OEWcDWYTFPxoB1StTpqZueraUYVEWisBg46m1kQAtIqhEPodC-USBnKFIztGWxzxXFX47Aag',
  • expires_in: 3599,
  • refresh_token: '1//0cAa_PK6AlemYCgYIARAAGAwSNwF-L9IrdUt1gzglxh5_L4b_PwoseFlQA1XDhqte7VMzDtg',
  • scope: 'openid www.googleapis.com/auth/userinfo.email',
  • token_type: 'Bearer',
  • id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRlZGMwMTJkMDdmNTJhZWRmZDVmOTc3ODRlMWJjYmUyM2MxOTcfobDCf2VrxXb6CCxoL_dZq1WnlEjBZx_Sf6Rg_tn3x4gWtusO1oe_bJx_gvlSLxtvSOdO_kPB2uGGQHr3xzF_Evr-S-BiGS8zMuIkslyN6fU7P7BdNVyOYAIYFvHikyIpAoesV2Fd2yBSngBwGmWfrHL7Z2415UrnlCG4H1Nw'

10. Сервер валидирует пришедший Token. Для этого нужен открытый ключ от Google. Актуальные ключи находятся по определенному адресу и периодически меняются:https://www.googleapis.com/oauth2/v1/certs.Поэтому надо или получать их каждый раз по этому адресу, либо хардкодить и следить за изменениями самому.Декодируем пришедший token, для нас самое главное это подтвержденный email пользователя:
decodedIdToken: {iss: 'https://accounts.google.com',azp: '918962537901-gi8oji3qk312pqdhg5bcju4hpr3efknv.apps.googleusercontent.com',aud: '918962537901-gi8oji3qk312pqdhg5bcju4hpr3efknv.apps.googleusercontent.com',sub: '101987547227421522632',email: 'work.test.mail.222@gmail.com',email_verified: true,at_hash: 'wmbkGMnAKOnfAKtGQFpXQw',iat: 1606220748,exp: 1606224348}

11. Проверяем, что email пользователя существует, или создаём нового пользователя c email из токена. При этомSessionId и полученные от Google токены сохраняются в базе.
12. Только теперь отвечаем Google HTTP кодом 307 (Redirect) и заголовком Location c адресом на клиенте:
HTTP Location:http://localhost:8080/googleLogin

13. И только теперь Google отвечает браузеру с тем же кодом307 (Redirect) и заголовком Location с заданным нами адресом
14. Браузер переходит на адрес, указанный вLocation иклиент определяет, что произошла успешная аутентификация пользователя с помощью Google аккаунта
15. Клиент, по сохраненному в sessionStorage SessionId, получает на сервере токен и данные пользователя
16. Клиент сохраняет токен в localStorage браузера

На этом процедура аутентификации с помощью Google аккаунта завершена и можно переходить к штатной работе приложения.

Прием, обработка и визуализация данных


Сделаем стандартный вид приложения, как рекомендует Google:
  • Drawer (navigation-drawer) в левой стороне
  • Меню сверху (v-app-bar)
  • Footer внизу (v-footer)
  • Для визуализации полученных данных используем карточки (Data Cards)

image

Для создания новых данных используется простая форма ввода:

image

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

  1. Для Vue есть библиотекаVuex, реализующаяstate management pattern.
  2. Есть универсальная библиотека Redux основанная на Flux от Facebook. Она реализует концепцию состояний, используя понятия action, state, view.

  • В примере я использовал Vuex.
  • Также необходимRouter для реализации переходов между страницами.

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

Архитектура


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

Заключение


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

Исходники на Github:https://github.com/AlexeySushkov/common-test-db
Пример запущен в облаке Amazon, можно посмотреть, как он выглядит вживую:http://globalid.tech/

Остается только в канун Нового Года пожелать, чтобы сбылись все ваши самые заветные PoC!

It's only the beginning!
Подробнее..

Перевод Отправляем магические ссылки с помощью Node.js

18.06.2020 02:12:19 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Разработчик Node.js.



Самый популярный метод входа в приложение это предоставление логина и пароля, но пользователи могут найти в этом определенные недостатки. Несмотря на то, что существует множество менеджеров паролей, их использование требует времени. Не сильно много, но все же. Некоторые приложения продвигают сложную и тем самым разочаровывающую политику паролей, поэтому придумать новый пароль бывает совсем непросто. Беспарольная система может стать хорошей альтернативой обычной аутентификации по логину и паролю. Она используется в Slack и FramerX, и выглядит это, как отправка магической ссылки пользователю для включения такой аутентификации в приложении. Беспарольная система работает следующим образом:

  1. Пользователь посещает приложение, где есть форма для ввода адреса электронной почты;
  2. Пользователь вводит адрес электронной почты и нажимает кнопку подтверждения;
  3. Магическая ссылка для входа в систему отправляется на электронную почту пользователя;
  4. Пользователь нажимает на ссылку и его перенаправляют в приложение, где он уже вошел в систему.


После этого магическая ссылка более недействительна.
Ниже мы рассмотрим некоторые основные моменты реализации беспарольной авторизации на Node.js.

Создадим express приложение

Для начала нам нужно создать простое express-приложение и добавить несколько конечных точек. Код ниже отвечает за настройку express-сервера с помощниками для парсинга входящих запросов, логином и добавлением cors. Нам нужно будет создать две директории, к которым мы позже добавим обработчики. Это будут /login и /account. Первый обработчик будет отвечать за отправку магических ссылок пользователям, а второй за их аутентификацию.


server.ts

Настройка nodemailer


Мы будем использовать nodemailer для отправки электронных писем, но для начала его нужно настроить. Нам понадобится несколько составляющих: адрес электронной почты, пароль от электронной почты, хост SMTP-сервера и порт SMTP-сервера. Также нам понадобится шаблон письма, которое мы будем отправлять пользователям с магической ссылкой. Шаблон может быть просто строкой или HTML-страницей.



Генерация токенов


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


token.ts

Отправка электронного письма


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



Аутентификация пользователя


При входе пользователя в системы, нам нужно авторизовать его запросы к нашему серверу. Для этого нужно сделать следующее:
  1. Проверить, есть ли заголовок авторизации в запросе;
  2. Проверить, что заголовок авторизации это bearer token;
  3. Попробовать декодировать этот токен;
  4. Проверить, что в токене есть все нужные поля. В нашем случае: email и expiration.
  5. Сравнить дату и время из expiration с текущими, чтобы проверить, что токен не истек.
  6. Наконец, проверить, что пользователь с электронной почтой, извлечённой из токена, существует.




Заключение


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

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



Узнать подробнее о курсе.


Подробнее..

Перевод Julia готова для прода

22.09.2020 10:23:41 | Автор: admin


Сейчас мне хочется поделиться своими выводами сделанными после нескольких бесед, в которых я участвовал на JuliaCon 2020.


Я потратил уже 20 лет на развертывание в корпоративных средах проектов связанных с наукой о данных (тогда она так еще не называлась, но мы уже обучали нейронные сети делать прогнозы), и у меня есть много коллег, которые глубоко занимаются разработкой корпоративного программного обеспечения. Процитирую Томаша Ольчака, который воистину является армией из одного человека во время реализации сложных корпоративных проектов:


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

В течение многих лет я бы с этим согласился, но после JuliaCon 2020, я думаю, мы можем с уверенностью заявить, что


Джулия готова идти в производство!


Позвольте мне теперь привести список ключевых (на мой взгляд) презентаций, сделанных во время JuliaCon 2020, которые заставили меня сделать этот вывод.


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


Создание микросервисов и приложений


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


Еще будет интересно:


  • Доклад по характеристикам приложений, где Кристоффер Карлссон покажет как создать исполняемые файлы, которые могут быть запущены на машинах, на которых не установлена Julia.
  • Презентация Julia for scripting, в ходе которой [Фредрик Экре]() обсуждает лучшие практики использования Julia в контекстах, где вам нужно много раз выполнять короткие фрагменты кода.
  • Доклад Genie.jl, в котором Адриан Сальчану показывает зрелый, стабильный, производительный и многофункциональный фреймворк веб-разработки на Julia.

Управление зависимостями


Пара докладов Pkg.update() и What's new in Pkg показывает, что в настоящее время Julia имеет лучшие в своем классе функциональные возможности для управления зависимостями корпоративного уровня для ваших проектов. Список предоставляемых функций настолько велик, что здесь трудно перечислить их все.


Позвольте мне только упомянуть один конкретный инструмент в этой экосистеме BinaryBuilder.jl, который позволит взять программное обеспечение, написанное на компилируемых языках, таких как C, C++, Fortran, Go или Rust, и построить предварительно скомпилированные артефакты, которые могут быть легко использованы из пакетов Julia (что означает, что не нужна никакая компиляция на стороне клиента, когда вы устанавливаете пакеты, имеющие такие зависимости).


Интеграция с внешними библиотеками


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



Здесь стоит добавить, что Джулия уже много лет отлично интегрируется с Python, см. JuliaPy.


Хорошим всеобъемляющим примером выполнения некоторой реальной работы в Julia, требующей интеграции, является создание многоканальной беспроводной настройки динамиков, где показано, как легко сшивать всякие штуки вместе (и, в частности, с помощью ZMQ.jl, Opus.jl, PortAudio.jl, и DSP.jl).


Еще один интересный доклад, демонстрирующий возможности интеграции, это JSServe: Websites & Dashboards в Julia, который показывает высокопроизводительный фреймворк для легкого объединения интерактивных графиков, markdown, виджетов и простого HTML/Javascript в Jupyter / Atom / Nextjournal и на веб-сайтах.


Инструментарий разработчика


В паре замечательных докладов Juno 1.0 и Using VS Code рассказывается, что текущая поддержка IDE для Джулии в VS Code первоклассна. У вас есть все для полного счастья: анализ кода (статический и динамический), отладчик, рабочие области, интеграция с ноутбуками Jupyter и удаленные возможности.


Управление рабочими процессами в машинном обучении


Я не хочу охватывать множество различных алгоритмов ML, доступных в Julia изначально, так как их просто слишком много (и если чего-то не хватает, вы можете легко интегрировать его см. раздел возможности интеграции выше).


Однако в дополнение к конкретным моделям вам нужны фреймворки, позволяющие управлять рабочими процессами ML. В этой области есть две интересные презентации: MLJ: A machine learning toolbox for Julia, и AutoMLPipeline: A ToolBox for Building ML Pipelines. По моему опыту, такие инструменты имеют решающее значение, когда вы хотите перейти с вашими ML-моделями из песочницы дата-саянтиста в реальное производственное использование.


Выводы


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


  • модели корпоративного уровня для создания микросервисов, приложений и
    надежные инструменты управления зависимостями,
  • очень гибкие и мощные возможности для интеграции Julia с существующими кодовыми базами, которые не были написаны на Julia,
  • отличный инструмент разработчика в VSCode,
  • зрелые пакеты, которые помогут вам создать производственный код для развертывания ML-решений,

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


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


Готова Ли Джулия для прода? Вопросы и ответы с Богумилом Каминским


Статья профессора Каминского вызвала немного ажиотажа на Hacker News. Некоторые комментаторы высказывали сомнения в том, что Джулия может считаться готовой к производству из-за, в частности, документации, пакетов, инструментов и поддержки.


InfoQ решило поговорить с профессором Камински, чтобы лучше понять его позицию.


InfoQ: Не могли бы вы описать ваш быкграунд и вашу связь с Julia?


Bogumi Kamiski: Я профессор Варшавской школы экономики SGH, Польша, работаю в области операционных исследований и имитационного моделирования.
Я нахожусь в топ-5% участников языка Julia по количеству коммитов, внес значительный вклад в экосистему данных Julia и, в частности, один из основных мейнтейнеров DataFrames.jl.
Я занимаю второе место по ответу на тег [julia] в StackOverflow. Прежде чем перейти на полный рабочий день в академию, я более 10 лет руководил командой из нескольких сотен разработчиков и аналитиков, развертывающих проекты BI/DWH/data science для крупнейших польских корпораций и учреждений.


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


Kamiski: Здесь очень важно определить, что я понимаю под готовностью Джулии к проду.


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


В прошлом это было серьезной проблемой. Основные пакеты, как правило, часто меняли свой API; учебные пособия, созданные год назад, теперь не будут работать без получения обновлений и т. д. Это было естественное состояние, когда развивались язык и экосистема. Теперь я вижу, что эти вещи значительно изменились, особенно для ядра языка Julia, но аналогичные вещи происходят и в экосистеме пакетов.


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


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


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


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


Почему это так важно? Ну, вы можете легко "отправить" проект Julia и ожидать, что любой человек в любой среде должен быть в состоянии относительно легко запустить его. Конечно, будут угловатые моменты; но мой опыт ежедневного использования Linux и Windows 10 заключается в том, что все должно просто работать на обеих платформах.


Если вы делаете проект в Julia, вы можете ожидать, что либо есть пакет, который делает то, что вы хотите, либо вы можете легко использовать код, написанный на C или Python, и заставить его работать. В своем посте я хотел подчеркнуть, что мы находимся именно в этом состоянии. В качестве примера, основанного на чем-то, над чем я работал на этой неделе, у Джулии есть отличный пакет LightGraphs.jl для работы с графиками, но мои сотрудники используют Python и предпочитают использовать igraph. Вот пример кода с использованием графика (взято из учебника для графика в Python):


import igraph as igg = ig.Graph()g.add_vertices(3)g.add_edges([(0,1), (1,2)])g.add_edges([(2, 0)])g.add_vertices(3)g.add_edges([(2, 3), (3, 4), (4, 5), (5, 3)])g.pagerank()

Теперь вы спросите, как бы выглядел эквивалент в Julia. А вот так:


using PyCallig = pyimport("igraph")g = ig.Graph()g.add_vertices(3)g.add_edges([(0,1), (1,2)])g.add_edges([(2, 0)])g.add_vertices(3)g.add_edges([(2, 3), (3, 4), (4, 5), (5, 3)])g.pagerank()

Как видите, он идентичен. Конечно, не во всех случаях это так просто, так как, например, словари в Julia и Python имеют разные синтаксисы, но это общее правило о том, как все работает. У вас даже есть вкладка-завершение и прямой доступ к строкам документов.


Что это означает на практике? Если вы делаете проект, вы не застреваете с мыслью: "могу ли я использовать Джулию, так как через три месяца, возможно, мне понадобится что-то в проекте, чего еще нет в Джулии"? Но скорее вы знаете: "я могу относительно безопасно использовать Julia, так как в настоящее время многие пакеты общего назначения уже существуют, и даже если чего-то не хватает, я могу просто использовать это с другого языка, и это будет относительно безболезненно, независимо от того, является ли это C/Python/R/...".


Также частью производственной готовности является то, что с PackageCompiler.jl вы можете создавать "приложения, которые представляют собой набор файлов, включая исполняемый файл, который может быть отправлен и запущен на других машинах без установки Julia на этой машине." Я не рассматриваю это как существенную часть готовности для продакшена (многие скриптовые языки, считающиеся готовыми к производству, не предоставляют этой опции), но во многих сценариях это хорошая функция.


Теперь позвольте мне уточнить, что я не вижу в определении термина "готовый к производству". Я не рассматриваю Джулию как язык, который лучше всего подходит для любого вида проекта. Каждый язык программирования имеет свою нишу, и ниша Julia это высокопроизводительные вычисления / наука о данных (или как вы это там называете). Если вам нужны компактные бинарные файлы наверняка Julia не является лучшим вариантом. Если вы хотите разрабатывать приложения для Android тут тоже посоветую смотреть в другом направлении.


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


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


Kamiski: Процитирую то, что я написал в начале своего поста:


"Я потратил 20 лет на развертывание проектов, связанных с наукой о данных, в корпоративных средах (тогда это не называлось наукой о данных, но мы уже обучали нейронные сети делать прогнозы), и у меня есть много коллег, которые глубоко занимаются разработкой корпоративного программного обеспечения."


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


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


Теперь, что касается таких вещей, как документация, пакеты, инструменты и поддержка конечно, это должно и может быть улучшено. И я согласен, что более зрелые экосистемы, такие как R/Python/Java, в среднем имеют здесь лучший охват. Например, как соразработчик DataFrames.jl, я могу сказать вам, что большинство последних PR связаны с документацией. Но я бы не стал недооценивать сообщество Джулии здесь. В общем, если у вас есть какой-либо вопрос и вы разместите его на SO, Julia Discourse или Julia Slack, вы можете получить ответ обычно в течение нескольких минут, самое большее часов. Вот типичная история такого рода: люди обычно очень отзывчивы, и ошибки исправляются довольно быстро.


Если бы меня попросили назвать главный сомнительный момент касательно Джулии, то это было бы наличие достаточного количества людей, квалифицированных в этом языке в общем сообществе разработчиков. Я могу понять владельцев продуктов / менеджеров проектов и их чувство риска того, что они не смогут найти достаточно людей для работы над своими проектами, как только они возьмут на себя смелость начать работать на Julia. Однако здесь я убежден, что ситуация быстро улучшается. В прошедшем JuliaCon 2020 приняли участие более 20 000 участников. Кроме того, есть много ресурсов, уже доступных бесплатно в интернете.


InfoQ: Помимо вопроса о том, готова ли Джулия к проду или нет, или для каких областей она предназначена, каковы, на ваш взгляд, основные сильные стороны языка? Рассматриваете ли вы его как замену Python, R или любому другому языку, по крайней мере в области научных вычислений и науки о данных?


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


  • Скорость здесь ситуация относительно проста. Возьмите любой зрелый пакет, такой как TensorFlow или PyTorch, который требует производительности; они написаны в основном на C++. А Python это всего лишь тонкая оболочка вокруг ядра C++. Теперь возьмем Flux.jl или Knet.jl. Они по существу реализованы в чистом виде. Итак, суть такова: если вам нужно, чтобы ваш код работал быстро, в то же время используя преимущества языка высокого уровня, то Julia это естественный выбор. Кроме того, как объяснялось выше, если есть внешняя библиотека, которая очень быстра, и вы хотите использовать ее, обычно это относительно легко сделать.


  • Простота использования есть множество проявлений этого аспекта, но мой опыт показывает, что когда кто-то получает представление о принципах Джулии, он очень хорошо сравнивается с точки зрения синтаксиса и дизайна, например, с R/Python при выполнении вычислений. Язык был разработан вокруг поддержки этих видов задач в лучшем виде. И это не только синтаксис это также выбор того, когда вещи будут происходить явно, а когда нет (например, с трансляцией). Исходя из моего опыта, это делает код Джулии простым в обслуживании, а хорошо написанный код в значительной степени самодокументируется.


  • Код является открытым и может быть изменен этот аспект имеет два измерения. Прежде всего, большинство пакетов Julia лицензированы MIT, что часто очень приветствуется в корпоративных средах. Во-вторых поскольку большинство пакетов написано на Julia, то если вам не нравится, как что-то работает вы просто модифицируете это сами (что гораздо проще, чем в R/Python, где, скорее всего, то, что вам нужно изменить, написано, например, на C, C++, Fortran).



Люди часто спрашивают меня, вижу ли я Джулию в качестве замены R / Python. И я думаю об этом так::


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


  • Если у вас есть новая задача, не требующая критической производительности просто используйте язык, который вы знаете лучше всего (и если для вас часто актуален пункт 1 как для меня вы можете смело выбрать для этого Julia).


  • Если у вас много устаревшего кода R/Python и вы им довольны просто придерживайтесь его и помните, что если у вас есть некоторые критически важные для производительности части, они могут быть относительно легко переписаны на Julia и интегрированы обратно с вашей исходной кодовой базой (я сделал много таких проектов).



InfoQ: Как вы видите эволюцию Джулии?


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


Если бы я назвал здесь некоторые основные изменения, то это были бы:


  • Улучшение поддержки многопоточности. Я думаю, что это действительно актуально для основных случаев использования Julia. Так-то поддержка есть, но все же здесь многое можно улучшить. Точно так же следует ожидать улучшения в обработке GPU/TPU.


  • Улучшение задержки компилятора. Опять же, с каждым релизом он становится намного лучше, но это как раз то, на улучшение чего все уповают.


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


  • Рост сообщества я считаю, что число людей, заинтересованных в Джулии, значительно увеличивается, о чем свидетельствует, например, количество участников JuliaCon2020. Это означает, что: а) в будущем будет легче нанимать качественных разработчиков Julia, если они понадобятся, и б) это создаст положительную обратную связь для качества экосистемы Julia, поскольку все больше людей сообщают о проблемах и участвуют в них. Опять же, как сопровождающий DataFrames.jl я наблюдаю этот сдвиг: люди, которые никогда не были в "ядре" разработки пакета, открывают вопросы / делают PR и обсуждают функциональные возможности в социальных сетях.



Если вы заинтересовались языком Julia, все доклады с JuliaCon 2020 доступны на YouTube.

Подробнее..

Аутентификация в IoT

24.12.2020 00:21:35 | Автор: admin

В последнее время постоянно растет количество умных вещей, таких как камеры, различные датчики, умные лампочки, выключатели и многое другое. Эти вещи имеют постоянный доступ в интернет и активно обмениваются данными для аналитики с приложениями. На самом деле существуют более стратегически важные данные, такие как показание датчиков о здоровье пациентов. Встает очень закономерный вопрос, как защищать данные такого рода, которые в реальном времени(без усмотрения пользователя) отпарвляются на сторонние устройства. Дело в том, что очень непросто спроектировать и построить уникальную и на сто процентов безопасную систему IoT(Internet of things), из-за того что устройства имеют различные операционные системы, цели и масштабы - некоторые работают в пределах вашей комнаты, а некоторые, например, должны охватить систему видеонаблюдения района города.

В этой статье мы рассмотрим, различные механизмы аутентификации - по имени пользователя и паролю, по токену, с помощью OTP(one-time password) и, наконец, сертификатов. Например, на бытовом уровне это позволяет защитить счётчики электроэнергии от неавторизованного доступа, и защитить данные от подмены.

Вернее всего будет начать с весьма фундаментальной вещи для IoT - платформы. Платформа IoT - это инструмент, который объединяет вещи и интернет и по сути является основой построений новых решений в IoT. Рынок платформ очень стремительно растет и рассматривать их все не имеет смысла и к тому же не является целью этой статьи. Поскольку общности это не нарушит, в качестве примера рассмотрим платформу от компании IBM(International Business Machines) .

Чтобы понять, в какие моменты должны срабатывать механизмы аутентификации давайте рассмотрим структуру стандартного IoT-приложения на основе IBM Watson IoT Platform и облачной платформы IBM Bluemix.

На Рис. 1 представлена структура IOT-приложения.На Рис. 1 представлена структура IOT-приложения.

Мы не будем вдаваться в подробности структуры, но если вкратце: устройства публикуют данные датчиков в IBM Watson IoT Platform, которая обменивается с IoT-приложением (на Bluemix) с помощью протокола MQTT - Message Queueing Telemetry Transport Protocol. Далее устройства получают от приложений инструкции по выполнению функций управления.

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

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

Аутентификация по имени пользователя и паролю

Суть в том, что при подключении устройства к брокеру(напрямую устройства никогда не соединяются), оно отосылает броекру имя пользователя и пароль, используя команду CONNECT. Пароль отправляется в виде открытого текста, если он не зашифрован.

После этого брокер отвечает сообщением CONNACK, в которм есть поле Return Code, которое по сути является ответом от сервера об успешной или неуспешной аутентификации. Далее клиент отправляет сообщение SUBSCRIBE брокеру и подписывается на получение сообщений для него.
Чтобы подтвердить подписку Брокер MQTT отправляет сообщение SUBACK обратно клиенту.

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

На Рис. 2 представлена схема взаимодействия устройства и брокера.На Рис. 2 представлена схема взаимодействия устройства и брокера.

Аутентификация по токену доступа

В этом варианте также используется команда CONNECT. Клиент извлекает токен доступа и отправляет его брокеру через поле password. С этим токеном брокер может проверить были ли изменены данные, и не истек ли срок действия токена.

Аутентификация на основе одноразового пароля (OTP)

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

Аутентификация на основе сертификатов

Не все брокеры поддерживают сертификаты устройств. Этот механизм используется в системах, где требования к безопасности высоки. Вообще говоря MQTT может использовать протокол транпортного уровня модели TCP/IP - TLS(Transport Layer Sequrity), и чтобы лучше понять как происходит установление соединения с помощью протокола TLS, рассмотрим этот процесс.

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

Как происходит соединение? Клиент высылает сообщение Client Hello, в которое вставляется набор шифров TLS, которые он поддерживает и Clent Random - число, которое используется для генерации ключей симметричного шифрования. Затем сервер шлёт сообщение Server Hello, где выбирает шифр TLS, из ранее ему предоставленных от клиента, а также Server Random(аналогично клиенту). Кроме этого сервер создает Идентификатор сесси, по которму позже можно восстановить ссесию не меняя ключи и алгоритмы шифрования, что само по себе хорошо, так как не нужно повторно проводить установку соединения. Далее сервер шлет клиенту сертификат - файл, содержащий ключ сервера. Получив сертификат, клиент его проверяет. Через некоторе время сервер шлет сообщение Server Key Exchange, для обмена ключами(это не обязательное сообщение, зависит от алгоритмов шифрования, которые были выбраны). Затем сервер отсылает Server Hello Done, после которого должен действовать клиент.

Далее идет процедура обмена ключами - клиент шлет Client Key Exchange, где передаёт информацию необходимую для получения разделяемого ключа(эта информация, опять же, зависит от алгоритма обмена ключами). Например в RSA используется открытый ключ сервера, который находился в сертификате, полученном от сервера и клиент генерирует pre-master secret(это предварительный ключ, на основе которого будут получены ключи симметричного шифрования). Используя открытый ключ из сертификата, клиент зашифровывает pre-master secret и отправляет на сервер в сообщении Client Key Exchange. Далее сервер получает этот ключ и с помощью своего закрытого ключа расшифровывает ранее полученное сообщение, таки образом он получает pre-master secret. Таким образом клиент и сервер имеют одинаковые ключи(pre-master secret), на основе которых они рассчитывают ключи для шифрования, используя Client Random и Server Random. Теперб всё готово чтобы переключиться на безопасное соединение и клиент отсылает Change Ciper Spec и Finished(это сообщение уже зашифровано), а сервер отвечает аналогичной парой сообщений.

Теперь все готово для передачи сообщений! А мы возвращаемся к IOT. В нашем слушае необходимо верифицировать еще и клиента! А поэтому перед Server Hello Done сервер запрашивает сертификат клиента. Сервер проверяет сертификат клиента, и если все успешно, то подлинность клиента также подтверждена. Это позволяет аутентифицировать клиента до установления безопасного соединения, что позволяет аутентифицировать клиента на транспортном уровне, а не на уровне приложений, а, следовательно, и до сообщения CONNECT в котором передаётся пароль и имя.

Стоит добавить, что при утечке данных, или каких-либо проблем с устройством, сервер должен сбросить такой сертификат и перестать подключать клиентов с этим сертификатом.

Итоги

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

Источники

1. http://blog.catchpoint.com/2017/05/30/protocol-for-internet-of-things/

2. https://developer.ibm.com/articles/iot-trs-secure-iot-solutions1/

3. https://iot-analytics.com/5-things-know-about-iot-platform/

Подробнее..

Демистификация JWT

09.12.2020 18:14:11 | Автор: admin

Вокруг JWT сложилась много распространенных заблуждений. Одно из них, например, что JWT зашифровано (на самом деле только подписано и закодировано base64). На практике, я часто встречаюсь с довольно странными решениям, когда в JWT хранится идентификатор сессии (на самом деле если вы работаете с сессией то JWT вам не нужно). Или же в JWT хранится идентификатор пользователя, профайл которого запрашивается при каждом запросе из базы данных (на самом деле JWT имеет смысл, если Вы хотите уменьшить количество запросов в базу данных). Или, что еще более странно сами JWT хранятся в базе данных.


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


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


Начнем с самой главной демистификации. JWT может выглядеть вот так:


eyJhbGciOiJSUzI1NiJ9.eyJpcCI6IjE3Mi4yMS4wLjUiLCJqdGkiOiIwNzlkZDMwMGFiODRlM2MzNGJjNWVkMTlkMjg1ZmRmZWEzNWJjYzExMmYxNDJiNmQ5M2Y3YmIxZWFmZTY4MmY1IiwiZXhwIjoxNjA3NTE0NjgxLCJjb3VudCI6MiwidHRsIjoxMH0.gH7dPMvf2TQaZ5uKVcm7DF4glIQNP01Dys7ADgsd6xcxOjpZ7yGhrgd3rMTHKbFyTOf9_EB5NEtNrtgaIsWTtCd3yWq21JhzbmoVXldJKDxjF841Qm4T6JfSth4vvDF5Ex56p7jgL3rkqk6WQCFigwwO2EJfc2ITWh3zO5CG05LWlCEOIJvJErZMwjt9EhmmGlj9B6hSsEGucCm6EDHVlof6DHsvbN2LM3Z9CyiCLNkGNViqr-jkDKbn8UwIuapJOrAT_dumeCWD1RYDL-WNHObaD3owX4iqwHss2yOFrUfdEynahX3jgzHrC36XSRZeEqmRnHZliczz99KeiuHfc56EF11AoxH-3ytOB1sMivj9LID-JV3ihaUj-cDwbPqiaFv0sL-pFVZ9d9KVUBRrkkrwTLVErFVx9UH9mHmIRiO3wdcimBrKpkMIZDTcU9ukAyaYbBlqYVEoTIGpom29u17-b05wY3y12lCA2n4ZqOceYiw3kyd46IYTGeiNmouG5Rb5ld1HJzyqsNDQJhwdibCImdCGhRuKQCa6aANIqFXM-XSvABpzhr1UmxDijzs30ei3AD8tAzkYe2cVhv3AyG63AcFybjFOU8cvchxZ97jCV32jYy6PFphajjHkq1JuZYjEY6kj7L-tBAFUUtjNiy_e0QSSu5ykJaimBsNzYFQ

Если его декодировать base64 миф о "секретности" сразу же разрушается:


{"alg":"RS256"}{"ip":"172.21.0.5","jti":"079dd300ab84e3c34bc5ed19d285fdfea35bcc112f142b6d93f7bb1eafe682f5","exp":1607514681,"count":2,"ttl":10} O2Mrn%!OPzN{hk11l\9Mkd    Z&WJP%^D8*X|!C&D0Di?Aknue7bB 6AV*9)S.jNv    `EcG9*6kQDv_xzEdgbs<wP("?K ?WxiHp<>,/EU]T-Q+\}Pfbu7ZTJ jhon-v 6j9:!z#fEewQ*44    bl"&t!F    *s>]+U&8z-@Fap2p\S}0hy*b1H/AU3bA$)   j)

Первая часть в фигурных скобках называется JOSE Header и описана https://tools.ietf.org/html/rfc7515. Может содержать поля, из которых наиболее важное alg. Если задать {"alg":"none"} токен считается валидным без подписи. И таким образом к Вашему API может получить доступ любой с токеном, сформированным вручную без подписи. В настоящее время большинство библиотек отвергают такие токены по умолчанию, но все же проверьте свои API на всякий случай.


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


И, наконец, подпись. Подпись может быть сформирована при помощи разных алгоритмов. Наиболее удобно, если алгоритмы основаны на паре ключей (публичном и приватном). Тогда проверку подписи можно проводить публичным ключом, который можно без опасения распространять между серверами, принимающими JWT.


Таким образом JWT это просто текст JSON, имеющий криптографическую подпись.


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


  1. Микросервисы. Данные (любые не обязательно авторизация, а например "корзина" с товарами и зафиксированными ценами товаров) формируются и подписываются на одном микросервисе, а используются на другом микросервисе, который проверяет подпись токена публичным ключом.


  2. Авторизация. Этот кейс может быть полезен и для монолита, если нужно сократить количество запросов в базу данных. При реализации "традиционной" сессии каждый запрос API генерирует дополнительный запрос к базе данных. С JWT все, что берется в базе данных помещается в JWT и подписывается.



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


Все же есть случаи, когда информация слишком чувствительна, и нужно иметь возможность аннулировать JWT еще до окончания его срока действия. Для этого формируется реестр (например в Redis) аннулированных токенов. Поскольку время действия токена небольшое реестр будет также небольшим, если удалять из реестра просроченные токены.


Следующий вопрос. Если время жизни JWT небольшое, то как обновлять JWT после окончания срока действия (это актуально для авторизационных токенов)? Для этого предлагается использовать второй, "условно-постоянный" токен с более продолжительным сроком действия. По истечении срока действия авторизационного токена его обновляют с использованием "условно-постоянного" токена.


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


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


apapacy@gmail.com
9 декабря 2020 года

Подробнее..
Категории: Api , Authentication , Json , Jwt , Authorization , Passport.js

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru