Samouczek: dodawanie uwierzytelniania i uprawnień do aplikacji podczas korzystania z usługi Azure Web PubSub

W artykule Tworzenie aplikacji do czatu przedstawiono sposób używania interfejsów API protokołu WebSocket do wysyłania i odbierania danych za pomocą usługi Azure Web PubSub. Zauważ, że dla uproszczenia nie wymaga to żadnego uwierzytelniania. Mimo że usługa Azure Web PubSub wymaga tokenu dostępu do nawiązania połączenia, interfejs API negotiate używany w tym samouczku do generowania tokenu dostępu nie wymaga uwierzytelniania. Każdy może wywołać ten interfejs API, aby uzyskać token dostępu.

W rzeczywistej aplikacji zazwyczaj chcesz, aby użytkownik zalogował się jako pierwszy, zanim będzie mógł korzystać z aplikacji. Z tego samouczka dowiesz się, jak zintegrować usługę Web PubSub z systemem uwierzytelniania i autoryzacji aplikacji, aby zwiększyć bezpieczeństwo.

Pełny przykład kodu tego samouczka można znaleźć w witrynie GitHub.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Włączanie uwierzytelniania w usłudze GitHub
  • Dodaj warstwę pośrednią uwierzytelniania do swojej aplikacji
  • Dodaj uprawnienia klientom

Ważne

Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych.

parametry połączenia zawiera informacje autoryzacyjne potrzebne Twojej aplikacji, aby uzyskać dostęp do usługi Azure Web PubSub. Klucz dostępu w parametry połączenia działa jak hasło root dla Twojej usługi. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi WebPubSubServiceClient.

Unikaj dystrybuowania kluczy dostępu do innych użytkowników, kodowania ich lub zapisywania ich w dowolnym miejscu w postaci zwykłego tekstu, który jest dostępny dla innych użytkowników. Obracanie kluczy, jeśli uważasz, że mogły one zostać naruszone.

Dodawanie uwierzytelniania do aplikacji pokoju rozmów

W tym samouczku jest ponownie użyta aplikacja do czatu utworzona w sekcji Tworzenie aplikacji do czatu. Możesz również sklonować kompletny przykładowy kod dla aplikacji do czatu z usługi GitHub.

W tym samouczku dodasz uwierzytelnianie do aplikacji czatu i zintegrujesz ją z usługą Web PubSub.

Najpierw dodaj uwierzytelnianie usługi GitHub do pokoju rozmów, aby użytkownik mógł zalogować się przy użyciu konta usługi GitHub.

  1. Instalowanie zależności.

    npm install --save cookie-parser
    npm install --save express-session
    npm install --save passport
    npm install --save passport-github2
    
  2. server.js Znajdź plik w katalogu i włącz uwierzytelnianie w usłudze GitHub, dodając następujący kod do pliku 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: '/' }));
    

    Powyższy kod używa Passport.js do włączenia uwierzytelniania w usłudze GitHub. Poniżej przedstawiono prostą ilustrację sposobu jej działania:

    1. /auth/github przekierowuje do github.com w celu zalogowania się.
    2. Po zalogowaniu GitHub przekierowuje Cię do /auth/github/callback z kodem dla Twojej aplikacji, aby dokończyć uwierzytelnianie. (Aby zobaczyć, jak profil zwrócony z GitHuba jest weryfikowany i zapisywany na serwerze, zajrzyj do callbacku `verified` w passport.use().)
    3. Po zakończeniu uwierzytelniania nastąpi przekierowanie do strony głównej (/) witryny.

    Aby uzyskać więcej informacji o usłudze GitHub OAuth i Passport.js, zobacz następujące artykuły:

    Aby to przetestować, należy najpierw utworzyć aplikację OAuth usługi GitHub:

    1. Przejdź do https://www.github.comstrony , otwórz swój profil, a następnie wybierz pozycję Ustawienia> dla deweloperów.
    2. Przejdź do Aplikacje OAuth i wybierz Nowa aplikacja OAuth.
    3. Wprowadź nazwę aplikacji i adres URL strony głównej (adres URL może być dowolny) i ustaw adres URL wywołania zwrotnego autoryzacji na http://localhost:8080/auth/github/callback. Ten adres URL odpowiada interfejsowi API wywołania zwrotnego, które udostępniłeś na serwerze.
    4. Po zarejestrowaniu aplikacji skopiuj identyfikator klienta i wybierz pozycję Wygeneruj nowy klucz tajny klienta.

    Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi WebPubSubServiceClient.

    Uruchom poniższe polecenie, aby przetestować ustawienia. Nie zapomnij zastąpić <connection-string>, <client-id> i <client-secret> swoimi wartościami.

    export WebPubSubConnectionString="<connection-string>"
    export GitHubClientId="<client-id>"
    export GitHubClientSecret="<client-secret>"
    node server
    

    Teraz otwórz plik http://localhost:8080/auth/github. Nastąpi przekierowanie do usługi GitHub w celu zalogowania się. Po zalogowaniu nastąpi przekierowanie do aplikacji do czatu.

  3. Zaktualizuj pokój rozmów, aby korzystać z tożsamości, którą otrzymujesz z usługi GitHub, zamiast monitować użytkownika o podanie nazwy użytkownika.

    Zaktualizuj public/index.html, aby bezpośrednio wywoływał(-a) /negotiate bez przekazywania identyfikatora użytkownika.

    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);
    

    Gdy użytkownik jest zalogowany, żądanie automatycznie przekazuje tożsamość użytkownika w pliku cookie. Dlatego wystarczy sprawdzić, czy użytkownik istnieje w req obiekcie, i dodać nazwę użytkownika do tokenu dostępu 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
      });
    });
    

    Teraz ponownie uruchom serwer, a gdy po raz pierwszy otworzysz pokój czatu, zobaczysz komunikat „brak autoryzacji”. Wybierz link logowania, aby się zalogować, a następnie zobaczysz, że działa tak jak wcześniej.

Praca z uprawnieniami

W poprzednich samouczkach nauczono się używać WebSocket.send(), aby bezpośrednio publikować komunikaty innym klientom przy użyciu podprotokołu. W rzeczywistej aplikacji możesz nie chcieć, aby klient mógł publikować lub subskrybować dowolną grupę bez kontroli uprawnień. W tej sekcji zobaczysz, jak kontrolować klientów przy użyciu systemu uprawnień usługi Web PubSub.

W usłudze Web PubSub klient może wykonywać następujące typy operacji przy użyciu podprotokołu:

  • Wysyłanie zdarzeń do serwera.
  • Publikowanie komunikatów w grupie.
  • Dołącz (subskrybuj) grupę.

Wysyłanie zdarzenia do serwera jest domyślną operacją klienta. Nie jest używany żaden protokół, więc zawsze jest dozwolony. Aby opublikować i zasubskrybować grupę, klient musi uzyskać uprawnienia. Istnieją dwa sposoby udzielania uprawnień do klientów przez serwer:

  • Określ role, gdy klient jest połączony (rola jest koncepcją reprezentowania uprawnień początkowych, gdy klient jest połączony).
  • Użyj interfejsu API, aby nadać klientowi uprawnienia po połączeniu.

Aby uzyskać uprawnienia do dołączenia do grupy, klient nadal musi dołączyć do grupy przy użyciu komunikatu "join group" po jego uzyskaniu uprawnienia. Alternatywnie serwer może użyć interfejsu API, aby dodać klienta do grupy, nawet jeśli nie ma uprawnień do dołączania.

Teraz użyjemy tego systemu uprawnień, aby dodać nową funkcję do pokoju rozmów. Do pokoju rozmów dodaje się nowy typ użytkownika o nazwie administrator . Zezwalasz administratorowi na wysyłanie komunikatów systemowych (komunikatów rozpoczynających się od "[SYSTEM]") bezpośrednio z klienta.

Najpierw należy oddzielić komunikaty systemowe i użytkowników do dwóch różnych grup, aby można było kontrolować ich uprawnienia oddzielnie.

Zmień server.js , aby wysyłać różne komunikaty do różnych grup:

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();
  }
});

Powyższy kod używa elementu WebPubSubServiceClient.group().sendToAll() do wysłania wiadomości do grupy zamiast do huba.

Ponieważ komunikat jest teraz wysyłany do grup, należy dodać klientów do grup, aby mogli kontynuować odbieranie komunikatów. Użyj programu obsługi handleConnect, aby dodać klientów do grup.

Uwaga

handleConnect jest wyzwalany, gdy klient próbuje nawiązać połączenie z usługą Web PubSub. W tym programie obsługi można zwracać grupy i role, aby usługa mogła dodać połączenie do grup lub przyznać role, gdy tylko zostanie nawiązane połączenie. Usługa może również użyć res.fail() polecenia , aby odmówić połączenia.

Aby wyzwolić handleConnect, przejdź do ustawień programu obsługi zdarzeń w portalu Azure i wybierz pozycję connect w sekcji zdarzeń systemowych.

Należy również zaktualizować kod HTML klienta, ponieważ teraz serwer wysyła komunikaty JSON zamiast zwykłego tekstu:

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 = '';
});

Następnie zmień kod klienta, aby wysyłał do grupy systemowej, gdy użytkownicy wybierają komunikat systemowy:

<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>

Domyślnie klient nie ma uprawnień do wysyłania do żadnej grupy. Zaktualizuj kod serwera, aby udzielić uprawnień użytkownikowi administracyjnemu (dla uproszczenia identyfikator administratora jest udostępniany jako argument wiersza polecenia).

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);
});

Teraz uruchom node server <admin-id>. Zobaczysz, że możesz wysłać komunikat systemowy do każdego klienta po zalogowaniu się jako <admin-id>.

Jeśli jednak zalogujesz się jako inny użytkownik, po wybraniu komunikatu systemowego nic się nie stanie. Możesz oczekiwać, że usługa otrzyma komunikat o błędzie, aby poinformować, że operacja nie jest dozwolona. Aby przekazać tę opinię, możesz ustawić ackId podczas publikowania wiadomości. Za każdym razem, gdy zostanie określony parametr ackId, usługa Web PubSub zwraca komunikat zawierający pasujący element ackId, wskazujący, czy operacja zakończyła się pomyślnie.

Zmień kod wysyłania komunikatu systemowego do następującego kodu:

let ackId = 0;
system.addEventListener('click', e => {
  ws.send(JSON.stringify({
    type: 'sendToGroup',
    group: 'system',
    ackId: ++ackId,
    dataType: 'text',
    data: message.value
    }));
  message.value = '';
});

Zmień również kod przetwarzania komunikatów w celu obsługi komunikatu 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;
  }
};

Teraz uruchom ponownie serwer i zaloguj się jako inny użytkownik. Podczas próby wysłania komunikatu systemowego jest wyświetlany komunikat o błędzie.

Kompletny przykładowy kod tego samouczka można znaleźć w witrynie GitHub.

Następne kroki

Ten samouczek przedstawia podstawy łączenia się z usługą Web PubSub oraz publikowania komunikatów do połączonych klientów przy użyciu podprotokołu.

Aby dowiedzieć się więcej na temat korzystania z usługi Web PubSub, przeczytaj inne samouczki dostępne w dokumentacji.