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 połączenia tokenu dostępu, negotiate interfejs API używany w 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
  • Dodawanie oprogramowania pośredniczącego uwierzytelniania do aplikacji
  • Dodawanie uprawnień do klientów

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 pliku 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 logowania.
    2. Po zalogowaniu usługa GitHub przekierowuje Cię do /auth/github/callback kodu aplikacji w celu ukończenia uwierzytelniania. (Aby zobaczyć, jak profil zwrócony z usługi GitHub jest weryfikowany i utrwalany na serwerze, zobacz zweryfikowane wywołanie zwrotne w pliku passport.use()).
    3. Po zakończeniu uwierzytelniania nastąpi przekierowanie do strony głównej (/) witryny.

    Aby uzyskać więcej informacji na temat uwierzytelniania OAuth w usłudze GitHub i pliku 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.compozycji , otwórz swój profil i wybierz pozycję Ustawienia> Ustawienia dewelopera.
    2. Przejdź do pozycji Aplikacje OAuth i wybierz pozycję 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 jest zgodny z interfejsem API wywołania zwrotnego uwidocznionego na serwerze.
    4. Po zarejestrowaniu aplikacji skopiuj identyfikator klienta i wybierz pozycję Wygeneruj nowy klucz tajny klienta.

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

    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 metodę do bezpośredniego wywołania /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);
    

    Po zalogowaniu użytkownika żądanie automatycznie przenosi tożsamość użytkownika za pośrednictwem 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 i zobaczysz komunikat "nieautoryzowany" po raz pierwszy otworzysz pokój rozmów. Wybierz link logowania, aby się zalogować, a następnie zobaczysz, że działa tak jak wcześniej.

Praca z uprawnieniami

W poprzednich samouczkach przedstawiono sposób używania WebSocket.send() funkcji do bezpośredniego publikowania komunikatów na innych klientach przy użyciu podprotocol. 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 z podprotocol:

  • 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 udzielić uprawnień do klienta po nawiązaniu połączenia.

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 WebPubSubServiceClient.group().sendToAll() polecenia do wysyłania komunikatu do grupy zamiast do centrum.

Ponieważ komunikat jest teraz wysyłany do grup, należy dodać klientów do grup, aby mogli kontynuować odbieranie komunikatów. handleConnect Użyj programu obsługi, 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 witrynie Azure Portal i wybierz pozycję Połącz w zdarzeniach 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 polecenie 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 ackId jest określony, usługa Web PubSub zwraca komunikat z dopasowaniem ackId , aby wskazać, czy operacja zakończyła się pomyślnie, czy nie.

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 zawiera podstawowe informacje na temat nawiązywania połączenia z usługą Web PubSub oraz publikowania komunikatów na połączonych klientach przy użyciu podprotocol.

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