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

Перевод Реализация технологии SSO на базе Node.js

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



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

Есть одно хорошее правило, касающееся систем, сложность которых со временем растёт, и взаимодействия этих систем с их пользователями. А именно, нагрузка по решению задач, связанных с усложнением архитектуры проекта, ложится на систему, а не на её пользователей. При этом неважно то, насколько сложны внутренние механизмы веб-проекта. Для пользователя он должен выглядеть единой системой. Иными словами, пользователь, работающий с веб-системой, состоящей из множества компонентов, должен воспринимать происходящее так, будто он работает с одной системой. В частности, речь идёт об аутентификации в таких системах с использованием SSO (Single Sign-On) технологии единого входа.

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

В современных условиях таким решениям препятствует широкое распространение микросервисных архитектур. Управление сессиями усложнилось в тот момент, когда при разработке веб-проектов стали использовать различные технологии, и когда разные службы иногда размещались на разных доменах. Кроме того, веб-службы, которые раньше писали на Java, начали писать, пользуясь возможностями платформы Node.js. Это усложнило работу с куки-файлами. Оказалось, что сессиями теперь управлять не так уж и просто.

Эти сложности привели к разработке новых методов входа в системы, в частности, речь идёт о технологии единого входа.

Технология единого входа


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

Мы, в учебных целях, собираемся реализовать технологию SSO на платформе Node.js.

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

Как организован вход в систему с использованием SSO?


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

Вот репозиторий с кодом проекта simple-sso, реализацию которого я здесь опишу. Я использую платформу Node.js, но вы можете реализовать то же самое и используя что-то другое. Давайте пошагово разберём действия пользователя, работающего с системой, и механизмы, из которых состоит эта система

Шаг 1


Пользователь пытается получить доступ к защищённому ресурсу системы (назовём этот ресурс потребителем SSO, sso-consumer). Потребитель SSO выясняет то, что пользователь не вошёл в систему, и перенаправляет пользователя на сервер SSO (sso-server), используя, в качестве параметра запроса, собственный адрес. На этот адрес будет перенаправлен пользователь, успешно прошедший проверку. Этот механизм представлен ПО промежуточного слоя для Express:

const isAuthenticated = (req, res, next) => {// простая проверка того, аутентифицирован ли пользователь,// если это не так - нужно перенаправить пользователя на SSO-сервер для входа в систему и// передать серверу текущий URL как URL, на который должен быть перенаправлен// пользователь, успешно прошедший проверкуconst redirectURL = `${req.protocol}://${req.headers.host}${req.path}`;if (req.session.user == null) {return res.redirect(`http://sso.ankuranand.com:3010/simplesso/login?serviceURL=${redirectURL}`);}next();};module.exports = isAuthenticated;

Шаг 2


SSO-сервер выясняет то, что пользователь в систему не вошёл, и перенаправляет его на страницу входа в систему:

const login = (req, res, next) => {// В req.query будет url, на который надо будет перенаправить пользователя//после успешного входа в систему, туда же надо передать sso-токен.// Эти данные о перенаправлении пользователя ещё можно использовать// для проверки источника поступления запросаconst { serviceURL } = req.query;// Попытка прямого доступа приведёт к ошибке в новом URL.if (serviceURL != null) {const url = new URL(serviceURL);if (alloweOrigin[url.origin] !== true) {return res.status(400).json({ message: "Your are not allowed to access the sso-server" });}}if (req.session.user != null && serviceURL == null) {return res.redirect("/");}// если сведения о пользователе уже имеются в глобальной сессии - перенаправить// пользователя с токеномif (req.session.user != null && serviceURL != null) {const url = new URL(serviceURL);const intrmid = encodedId();storeApplicationInCache(url.origin, req.session.user, intrmid);return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);}return res.render("login", {title: "SSO-Server | Login"});};

Сделаю тут некоторые комментарии относительно безопасности.

Мы проверяем serviceURL, поступающий в виде параметра запроса к SSO-серверу. Благодаря этому мы узнаём о том, зарегистрирован ли этот URL в системе, и о том, может ли представленная им служба пользоваться услугами SSO-сервера.

Вот как может выглядеть список URL служб, которым разрешено использование SSO-сервера:

const alloweOrigin = {"http://consumer.ankuranand.in:3020": true,"http://consumertwo.ankuranand.in:3030": true,"http://test.tangledvibes.com:3080": true,"http://blog.tangledvibes.com:3080": fasle,};

Шаг 3


Пользователь вводит имя пользователя и пароль, которые отправляются SSO-серверу в запросе на вход в систему.


Страница входа в систему

Шаг 4


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

Шаг 5


SSO-сервер берёт токен авторизации и передаёт его туда, откуда к нему пришёл только что авторизовавшийся пользователь (то есть передаёт токен потребителю SSO).

const doLogin = (req, res, next) => {// Выполнить проверку с использованием адреса электронной почты и пароля.// Тут мы не вдаёмся в подробности использования хранилищ данных, поэтому// userDB - это обычный объект, описанный тут же, в коде сервераconst { email, password } = req.body;if (!(userDB[email] && password === userDB[email].password)) {return res.status(404).json({ message: "Invalid email and password" });}// В противном случае выполнить перенаправлениеconst { serviceURL } = req.query;const id = encodedId();req.session.user = id;sessionUser[id] = email;if (serviceURL == null) {return res.redirect("/");}const url = new URL(serviceURL);const intrmid = encodedId();storeApplicationInCache(url.origin, id, intrmid);return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);};

Снова сделаю некоторые замечания о безопасности:

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

Шаг 6


Потребитель SSO получает токен и обращается к серверу SSO для проверки токена. Сервер проверяет токен и возвращает ещё один токен с информацией о пользователе. Этот токен используется потребителем SSO для создания сессии с пользователем. Эта сессия называется локальной.

Вот код ПО промежуточного слоя, используемого в потребителе SSO, построенном на основе Express:

const ssoRedirect = () => {return async function(req, res, next) {// проверяется, есть ли в req queryParameter, представляющий ssoToken,// и то, что именно является реферером.const { ssoToken } = req.query;if (ssoToken != null) {// для удаления ssoToken в параметре запроса, задающем перенаправление.const redirectURL = url.parse(req.url).pathname;try {const response = await axios.get(`${ssoServerJWTURL}?ssoToken=${ssoToken}`,{headers: {Authorization: "Bearer l1Q7zkOL59cRqWBkQ12ZiGVW2DBL"}});const { token } = response.data;const decoded = await verifyJwtToken(token);// теперь у нас есть декодированный jwt, поэтому используем// global-session-id как id сессии, что позволит// реализовать процедуру выхода из системы с использованием глобальной сессии.req.session.user = decoded;} catch (err) {return next(err);}return res.redirect(`${redirectURL}`);}return next();};};

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

В нашем случае сервер SSO, после успешной проверки токена, возвращает подписанный JWT с информацией о пользователе.

const verifySsoToken = async (req, res, next) => {const appToken = appTokenFromRequest(req);const { ssoToken } = req.query;// Если нет токена приложения или запрос на ssoToken недействителен.// Усли ssoToken отсутствует в кеше - значит, нас пытаются обмануть.if (appToken == null ||ssoToken == null ||intrmTokenCache[ssoToken] == null) {return res.status(400).json({ message: "badRequest" });}// Если appToken присутствует - проверяем его действительность для приложенияconst appName = intrmTokenCache[ssoToken][1];const globalSessionToken = intrmTokenCache[ssoToken][0];// Если appToken не соответствует токену, выданному выданному SSO-приложению при регистрации или на более поздней стадии работыif (appToken !== appTokenDB[appName] ||sessionApp[globalSessionToken][appName] !== true) {return res.status(403).json({ message: "Unauthorized" });}// проверяем, был ли сгенерирован переданный токенconst payload = generatePayload(ssoToken);const token = await genJwtToken(payload);// удаляем из кеша ключ, который больше использоваться не будетdelete intrmTokenCache[ssoToken];return res.status(200).json({ token });};

Вот некоторые замечания о безопасности.

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

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

const userDB = {"info@ankuranand.com": {password: "test",userId: encodedId(), // в том случае, если вы не хотите передавать адрес электронной почты пользователя.appPolicy: {sso_consumer: { role: "admin", shareEmail: true },simple_sso_consumer: { role: "user", shareEmail: false }}}};

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


Установка локальной и глобальной сессий

Краткий обзор потребителя SSO и сервера SSO


Давайте сделаем краткий обзор функционала потребителя SSO и сервера SSO.

Потребитель SSO


  1. Подсистема-потребитель SSO не выполняет аутентификацию пользователя, перенаправляя пользователя на сервер SSO.
  2. Эта подсистема получает токен, передаваемый ей сервером SSO.
  3. Она взаимодействует с сервером, проверяя действительность токена.
  4. Она получает JWT и проверяет этот токен с использованием публичного ключа.
  5. Эта подсистема устанавливает локальную сессию.

Сервер SSO


  1. Сервер SSO проверяет данные, вводимые пользователем для входа в систему.
  2. Сервер создаёт глобальную сессию.
  3. Он создаёт токен авторизации.
  4. Токен авторизации отправляется потребителю SSO.
  5. Сервер проверяет действительность токенов, передаваемых ему потребителями SSO.
  6. Сервер отправляет потребителю SSO JWT с информацией о пользователе.

Организация централизованного выхода из системы


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

  1. Если существует локальная сессия обязательно существует и глобальная сессия.
  2. Если существует глобальная сессия, это необязательно означает существование локальной сессии.
  3. Если локальная сессия уничтожается должна быть уничтожена и локальная сессия.

Итоги


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

Используются ли в ваших проектах механизмы SSO?
Источник: habr.com
К списку статей
Опубликовано: 01.09.2020 16:17:46
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании ruvds.com

Разработка веб-сайтов

Javascript

Node.js

Sso

Разработка

Категории

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

  • Имя: Макс
    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