Учебник. Добавление проверки подлинности и разрешений в приложение при использовании Azure Web PubSub
Из учебника по созданию приложения чата вы узнали, как использовать API WebSocket для отправки и получения данных с помощью Azure Web PubSub. Обратите внимание, что для простоты он не требует проверки подлинности. Хотя в Azure Web PubSub для подключения необходим маркер доступа, интерфейс API negotiate
, который использовался в этом учебнике для создания маркера доступа, не требует проверки подлинности. Любой пользователь может вызвать этот API для получения маркера доступа.
В реальных приложениях обычно требуется, чтобы пользователь сначала выполнил вход, чтобы получить возможность использовать ваше приложение. Из этого учебника вы узнаете, как интегрировать Web PubSub с системой проверки подлинности и авторизации приложения, чтобы сделать его более безопасным.
Полный пример кода для работы с этим учебником можно найти на сайте GitHub.
В этом руководстве описано следующее:
- Включение проверки подлинности GitHub
- Добавление ПО промежуточного слоя для проверки подлинности в приложение
- Добавление разрешений в клиенты
Внимание
Необработанные строка подключения отображаются в этой статье только для демонстрационных целей.
Строка подключения содержит сведения об авторизации, требуемые для доступа приложения к службе Azure Web PubSub. Ключ доступа в строке подключения аналогичен паролю привилегированного пользователя для службы. В рабочих средах всегда защищать ключи доступа. Используйте Azure Key Vault для безопасного управления ключами и защиты подключения.WebPubSubServiceClient
Старайтесь не распространять ключи доступа среди других пользователей, жестко программировать их или где-то сохранять в виде обычного текста в открытом доступе для других пользователей. Меняйте свои ключи постоянно, если предполагаете, что они могут быть подобраны.
Добавление проверки подлинности для приложения комнаты чата
В этом учебнике используется приложение чата, созданное в учебнике по созданию приложения чата. Вы также можете клонировать полный пример кода для приложения чата с GitHub.
Из этого учебника вы узнаете, как добавить проверку подлинности в приложение чата и интегрировать ее с Web PubSub.
Сначала добавьте проверку подлинности GitHub в комнату чата, чтобы пользователь мог использовать для входа учетную запись GitHub.
Установите зависимости.
npm install --save cookie-parser npm install --save express-session npm install --save passport npm install --save passport-github2
Найдите файл в каталоге
server.js
и включите проверку подлинности GitHub, добавив следующий кодserver.js
:const app = express(); const users = {}; passport.use( new GitHubStrategy({ clientID: process.argv[3], clientSecret: process.argv[4] }, (accessToken, refreshToken, profile, done) => { users[profile.id] = profile; return done(null, profile); } )); passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { if (users[id]) return done(null, users[id]); return done(`invalid user id: ${id}`); }); app.use(cookieParser()); app.use(session({ resave: false, saveUninitialized: true, secret: 'keyboard cat' })); app.use(passport.initialize()); app.use(passport.session()); app.get('/auth/github', passport.authenticate('github', { scope: ['user:email'] })); app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/' }));
В приведенном выше коде для включения проверки подлинности GitHub используется Passport.js. Ниже приведена простая иллюстрация принципов работы.
/auth/github
перенаправляет пользователя на github.com для входа.- После входа GitHub перенаправит вас на страницу
/auth/github/callback
с кодом для приложения, чтобы завершить проверку подлинности. (Чтобы узнать, как профиль, возвращенный из GitHub, проверяется и сохраняется на сервере, см. пример обратного вызова вpassport.use()
.) - После завершения проверки подлинности вы будете перенаправлены на домашнюю страницу (
/
) сайта.
Дополнительные сведения об OAuth и Passport.js на GitHub см. в следующих статьях:
Для тестирования сначала необходимо создать приложение OAuth GitHub:
- Перейдите на сайт https://www.github.com, откройте свой профиль и выберите Settings>Developer settings (Параметры > Параметры разработчика).
- Перейдите к приложениям OAuth, а затем выберите New OAuth App (Создать приложение OAuth).
- Укажите имя приложения и URL-адрес домашней страницы (URL-адрес может быть любым) и задайте для параметра Authorization callback URL (URL-адрес обратного вызова авторизации) значение
http://localhost:8080/auth/github/callback
. Этот URL-адрес соответствует API обратного вызова, который вы указывали на сервере. - После регистрации приложения скопируйте идентификатор клиента и щелкните Generate a new client secret (Сгенерировать новый секрет клиента).
Необработанные строка подключения отображаются в этой статье только для демонстрационных целей. В рабочих средах всегда защищать ключи доступа. Используйте Azure Key Vault для безопасного управления ключами и защиты подключения.
WebPubSubServiceClient
Выполните приведенную ниже команду, чтобы проверить настройки. Не забудьте заменить
<connection-string>
,<client-id>
и<client-secret>
своими значениями.export WebPubSubConnectionString="<connection-string>" export GitHubClientId="<client-id>" export GitHubClientSecret="<client-secret>" node server
Теперь откройте
http://localhost:8080/auth/github
. Система перенаправит вас на GitHub для входа. После входа вы будете перенаправлены в приложение чата.Обновите комнату чата, чтобы использовать удостоверение, полученное от GitHub, вместо запроса имени пользователя.
Обновите
public/index.html
для прямого вызова/negotiate
без передачи идентификатора пользователя.let messages = document.querySelector('#messages'); let res = await fetch(`/negotiate`); if (res.status === 401) { let m = document.createElement('p'); m.innerHTML = 'Not authorized, click <a href="/auth/github">here</a> to login'; messages.append(m); return; } let data = await res.json(); let ws = new WebSocket(data.url);
При входе пользователя в систему запрос будет автоматически передавать удостоверение пользователя через файл cookie. Поэтому вам нужно лишь проверить, существует ли пользователь в объекте
req
, и добавить имя пользователя в маркер доступа Web PubSub:app.get('/negotiate', async (req, res) => { if (!req.user || !req.user.username) { res.status(401).send('missing user id'); return; } let options = { userId: req.user.username }; let token = await serviceClient.getClientAccessToken(options); res.json({ url: token.url }); });
Теперь повторно запустите сервер, и вы увидите сообщение "не авторизовано" в первый раз, когда вы открываете комнату чата. Выберите ссылку для входа, а затем вы увидите, что она работает раньше.
Работа с разрешениями
В предыдущих учебниках вы узнали, как использовать WebSocket.send()
, чтобы напрямую публиковать сообщения в других клиентах с помощью подпротокола. В реальном приложении может быть так, что у клиента не должно быть возможности выполнять публикацию в любой группе или подписываться на нее без контроля разрешений. В этом разделе описано, как управлять клиентами с помощью системы разрешений Web PubSub.
В Web PubSub клиент может выполнять следующие типы операций с подпротоколом:
- отправка событий на сервер;
- публикация сообщений в группе;
- присоединение к группе (подписка на нее).
Отправка события на сервер является для клиента операцией по умолчанию. Протокол не используется, поэтому он всегда разрешен. Для публикации в группе и подписки на нее клиенту необходимо получить разрешение. Существует два способа предоставления разрешения клиентам со стороны сервера:
- указание ролей при подключении клиента (роль — это концепция, представляющая исходные разрешения при подключении клиента);
- использование интерфейса API для предоставления разрешения клиенту после его подключения.
В случае с разрешением на присоединение к группе клиент по-прежнему должен присоединиться к группе с помощью соответствующего сообщения после того, как он получит разрешение. Другой вариант предусматривает, что сервер может также использовать интерфейс API для добавления клиента в группу, даже если у него нет разрешения на присоединение.
Теперь давайте используем эту систему разрешений для добавления новой функции в комнату чата. Вы добавляете в комнату чата новый тип пользователя, вызываемого администратором . Администратор может отправлять системные сообщения (сообщения, начинающиеся с "[SYSTEM]") непосредственно от клиента.
Сначала необходимо разделить системные и пользовательские сообщения на две разные группы, чтобы соответствующие разрешения можно было контролировать отдельно.
Измените server.js
для отправки разных сообщений в разные группы:
let handler = new WebPubSubEventHandler(hubName, {
path: '/eventhandler',
handleConnect: (req, res) => {
res.success({
groups: ['system', 'message'],
});
},
onConnected: req => {
console.log(`${req.context.userId} connected`);
serviceClient.group('system').sendToAll(`${req.context.userId} joined`, { contentType: 'text/plain' });
},
handleUserEvent: (req, res) => {
if (req.context.eventName === 'message') {
serviceClient.group('message').sendToAll({
user: req.context.userId,
message: req.data
});
}
res.success();
}
});
Приведенный выше код использует WebPubSubServiceClient.group().sendToAll()
для отправки сообщения в группу, а не в концентратор.
Так как сообщение теперь отправляется в группы, нам нужно добавить в группы клиенты, чтобы они могли продолжать получать сообщения. Используйте обработчик handleConnect
, чтобы добавить клиенты в группы.
Примечание.
handleConnect
активируется, когда клиент пытается подключиться к Web PubSub. В этом обработчике можно вернуть группы и роли, чтобы служба могла добавить подключение к группам или предоставить роли, как только подключение будет установлено. Служба также может использовать res.fail()
для запрета подключения.
Для активации handleConnect
перейдите к параметрам обработчика событий на портале Azure и выберите пункт Подключить в системных событиях.
Кроме того, необходимо обновить код HTML клиента, так как теперь сервер отправляет сообщения в формате JSON вместо обычного текста:
let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
ws.onopen = () => console.log('connected');
ws.onmessage = event => {
let m = document.createElement('p');
let message = JSON.parse(event.data);
switch (message.type) {
case 'message':
if (message.group === 'system') m.innerText = `[SYSTEM] ${message.data}`;
else if (message.group === 'message') m.innerText = `[${message.data.user}] ${message.data.message}`;
break;
}
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(JSON.stringify({
type: 'event',
event: 'message',
dataType: 'text',
data: message.value
}));
message.value = '';
});
Затем измените код клиента для отправки сообщения в системную группу, когда пользователь выбирает системное сообщение:
<button id="system">system message</button>
...
<script>
(async function() {
...
let system = document.querySelector('#system');
system.addEventListener('click', e => {
ws.send(JSON.stringify({
type: 'sendToGroup',
group: 'system',
dataType: 'text',
data: message.value
}));
message.value = '';
});
})();
</script>
По умолчанию клиент не имеет разрешения на отправку в какую-либо группу. Обновите код сервера для предоставления разрешения администратору (для простоты идентификатор администратора указывается в виде аргумента командной строки).
app.get('/negotiate', async (req, res) => {
...
if (req.user.username === process.argv[2]) options.claims = { role: ['webpubsub.sendToGroup.system'] };
let token = await serviceClient.getClientAccessToken(options);
});
Теперь воспользуйтесь командой node server <admin-id>
. Вы видите, что при входе <admin-id>
в систему можно отправить системное сообщение каждому клиенту.
Однако если вы войдете в систему от имени другого пользователя, то при выборе системного сообщения ничего не произойдет. Возможно, потребуется, чтобы служба выдавала ошибку с сообщением о том, что операция не разрешена. Чтобы обеспечить такой отзыв, можно задать ackId
при публикации сообщения. При ackId
каждом указании Web PubSub возвращает сообщение с соответствующим ackId
значением, чтобы указать, выполнена ли операция успешно или нет.
Измените код для отправки системного сообщения на следующий:
let ackId = 0;
system.addEventListener('click', e => {
ws.send(JSON.stringify({
type: 'sendToGroup',
group: 'system',
ackId: ++ackId,
dataType: 'text',
data: message.value
}));
message.value = '';
});
Кроме того, измените код для обработки сообщений так, чтобы он обрабатывал сообщение ack
:
ws.onmessage = event => {
...
switch (message.type) {
case 'ack':
if (!message.success && message.error.name === 'Forbidden') m.innerText = 'No permission to send system message';
break;
}
};
Теперь перезапустите сервер и выполните вход от имени другого пользователя. При попытке отправить системное сообщение появится сообщение об ошибке.
Полный пример кода для этого руководства см. на сайте GitHub.
Следующие шаги
В этом учебнике показано, как подключиться к службе Web PubSub и публиковать сообщения для подключенных клиентов с помощью подпротокола.
Дополнительные сведения об использовании службы Web PubSub см. в других учебниках, доступных в документации.