Kanały szyfrowane
Kanał, którego nazwa zaczyna się od private-encrypted-, jest szyfrowany end-to-end. Twój serwer szyfruje każdy payload przed opublikowaniem, a zatwierdzone klienty przeglądarkowe odszyfrowują go kluczem z Twojego endpointu autoryzacji. Krawędź Realtime i pośrednicy sieciowi widzą wyłącznie szyfrogram.
Wygeneruj i przechowaj 32-bajtowy klucz główny. Klucz główny nigdy nie pojawia się w żądaniu API Realtime, a prefiks nazwy kanału włącza tę funkcję. Bird nie może odzyskać utraconego klucza, a payloady zaszyfrowane tym kluczem pozostają nieczytelne po jego wymianie.
Kanały szyfrowane korzystają z tego samego endpointu i podpisu co kanały prywatne. Odpowiedź autoryzacji zawiera również pochodny klucz deszyfrujący kanału jako shared_secret. Odrzucenie subskrypcji uniemożliwia klientowi otrzymanie klucza.
Generowanie klucza głównego
Wygeneruj 32 losowe bajty, zakoduj je w base64 i przechowaj wartość tak jak sekret aplikacji:
Przykład kodu
openssl rand -base64 32Przekaż go do SDK serwera jako część konfiguracji realtime, obok klucza aplikacji i sekretu.
Publikowanie zaszyfrowanego zdarzenia
SDK serwera wykrywa prefiks kanału, wyprowadza klucz z klucza głównego i szyfruje payload JSON lokalnie. Żądanie publikacji zawiera zaszyfrowaną kopertę.
import { BirdClient } from "@messagebird/sdk";
const bird = new BirdClient({
apiKey: process.env.BIRD_API_KEY,
realtime: {
key: process.env.BIRD_REALTIME_KEY,
secret: process.env.BIRD_REALTIME_SECRET,
encryptionMasterKey: process.env.BIRD_REALTIME_MASTER_KEY,
},
});
await bird.realtime.publish("rap_01krdgeqcxet5s7t44vh8rt9mg", {
event: "order.updated",
channels: ["private-encrypted-orders"],
data: { order_id: "ord_123", status: "shipped" },
});from bird import Bird
client = Bird(
realtime_key=os.environ["BIRD_REALTIME_KEY"],
realtime_secret=os.environ["BIRD_REALTIME_SECRET"],
realtime_encryption_master_key=os.environ["BIRD_REALTIME_MASTER_KEY"],
)
client.realtime.publish(
"rap_01krdgeqcxet5s7t44vh8rt9mg",
event="order.updated",
channels=["private-encrypted-orders"],
data={"order_id": "ord_123", "status": "shipped"},
)client, err := bird.NewClient(
option.WithAPIKey(os.Getenv("BIRD_API_KEY")),
option.WithRealtimeCredentials(os.Getenv("BIRD_REALTIME_KEY"), os.Getenv("BIRD_REALTIME_SECRET")),
option.WithRealtimeEncryptionMasterKey(os.Getenv("BIRD_REALTIME_MASTER_KEY")),
)
if err != nil {
log.Fatal(err)
}
_, err = client.Realtime.Publish(context.Background(), "rap_01krdgeqcxet5s7t44vh8rt9mg", bird.RealtimePublishParams{
Event: "order.updated",
Channels: []string{"private-encrypted-orders"},
Data: map[string]any{"order_id": "ord_123", "status": "shipped"},
})$bird = new Bird(getenv('BIRD_API_KEY'), realtime: new RealtimeOptions(
key: getenv('BIRD_REALTIME_KEY'),
secret: getenv('BIRD_REALTIME_SECRET'),
encryptionMasterKey: getenv('BIRD_REALTIME_MASTER_KEY'),
));
$bird->realtime->publish('rap_01krdgeqcxet5s7t44vh8rt9mg', (new RealtimePublish())
->setEvent('order.updated')
->setChannels(['private-encrypted-orders'])
->setData(['order_id' => 'ord_123', 'status' => 'shipped']));Zaszyfrowany kanał musi być jedynym kanałem w pojedynczej publikacji. Każdy zaszyfrowany kanał wyprowadza inny klucz, więc pozostałe kanały nie mogłyby odszyfrować tego samego zaszyfrowanego payloadu. SDK-i odrzucają ten fan-out lokalnie, a API zwraca E23000, jeśli go otrzyma. Aby opublikować na kilku zaszyfrowanych kanałach, użyj batcha z jednym kanałem na zdarzenie.
Zwracanie wspólnego sekretu z endpointu autoryzacji
Twój endpoint autoryzacji zatwierdza zaszyfrowane subskrypcje tak samo jak prywatne. Użyj helpera authorizeChannel z SDK, a odpowiedź automatycznie otrzyma shared_secret, gdy nazwa kanału zawiera prefiks szyfrowania:
app.post("/bird/auth", async (req, res) => {
const { connection_id, channel_name } = req.body;
const user = getUserFromSession(req);
if (!user || !mayJoin(user, channel_name)) return res.sendStatus(403);
res.json(
await bird.realtime.authorizeChannel({
connectionId: connection_id,
channelName: channel_name,
}),
);
});@app.post("/bird/auth")
def bird_auth():
body = request.get_json()
user = get_user_from_session()
if user is None or not may_join(user, body["channel_name"]):
abort(403)
return client.realtime.authorize_channel(
connection_id=body["connection_id"],
channel_name=body["channel_name"],
)func birdAuth(w http.ResponseWriter, r *http.Request) {
var body struct {
ConnectionID string `json:"connection_id"`
ChannelName string `json:"channel_name"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
user, ok := userFromSession(r)
if !ok || !mayJoin(user, body.ChannelName) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
auth, err := client.Realtime.AuthorizeChannel(bird.RealtimeChannelAuthorizationParams{
ConnectionID: body.ConnectionID,
ChannelName: body.ChannelName,
})
if err != nil {
http.Error(w, "authorization failed", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(auth)
}function birdAuth(string $connectionId, string $channelName, User $user): array
{
if (!mayJoin($user, $channelName)) {
http_response_code(403);
exit;
}
return $bird->realtime->authorizeChannel($connectionId, $channelName);
}SDK wyprowadza osobny shared_secret dla każdego kanału. Autoryzacja dla private-encrypted-orders nie pozwala więc odszyfrować private-encrypted-invoices. Sekret przesyłany jest w odpowiedzi autoryzacji i nie jest dołączany do ramki subskrypcji wysyłanej do krawędzi.
Subskrypcja i deszyfrowanie w przeglądarce
Szyfr korzysta z osobnego punktu wejścia @messagebird/realtime/encrypted. Zaimportuj go i przekaż jako opcję encryption klienta:
Przykład kodu
import { BirdRealtime } from "@messagebird/realtime";
import { encryption } from "@messagebird/realtime/encrypted";
const bird = new BirdRealtime({
appKey: "your-app-key",
region: "us1",
authEndpoint: "/bird/auth",
encryption,
});
const orders = bird.subscribe("private-encrypted-orders");
orders.bind("order.updated", (data) => {
console.log(data); // decrypted: { order_id: "ord_123", status: "shipped" }
});Bindingi otrzymują tekst jawny. Subskrypcja bez opcji encryption natychmiast rzuca wyjątek, a odpowiedź autoryzacji bez shared_secret powoduje niepowodzenie subskrypcji.
Obecnie tylko klient przeglądarkowy obsługuje kanały szyfrowane. Klienty Swift i Kotlin odrzucają subskrypcje private-encrypted-, ponieważ nie implementują deszyfrowania.
Rotacja klucza głównego
Wdróż nowy klucz jednocześnie na wszystkich publisherach i endpointach autoryzacji. Podczas rotacji:
- Nowe publikacje są szyfrowane nowym kluczem.
- Zasubskrybowany klient przeglądarkowy, który nie może odszyfrować zdarzenia, ponownie się autoryzuje i pobiera nowy shared_secret.
- Instancje korzystające z różnych kluczy głównych mogą chwilowo publikować zdarzenia, których niektóre klienty nie potrafią odszyfrować, więc koordynuj wdrożenie między instancjami.
Dokonaj rotacji klucza w przypadku wycieku lub utraty. Rotacja chroni przyszłe payloady, ale nie może ponownie zaszyfrować wcześniejszych zdarzeń ani unieważnić kopii starego klucza.
Czego kanały szyfrowane nie robią
- Oficjalne klienty nie obsługują zdarzeń klienckich. Przeglądarkowy trigger() rzuca wyjątek na kanałach szyfrowanych, ponieważ klient nie szyfruje payloadów klient-klient. Nie wysyłaj jawnych zdarzeń klienckich z niestandardowego klienta.
- Obecność i szyfrowanie nie mogą być łączone. Prefiks presence-encrypted- nie jest obsługiwany. Cache i szyfrowanie działają razem: kanały private-encrypted-cache- przechowują zbuforowane zdarzenie w postaci zaszyfrowanej, choć po rotacji klucza zbuforowana kopia pozostaje zaszyfrowana starym kluczem, dopóki kolejna publikacja jej nie zastąpi.
- Nazwy kanałów i nazwy zdarzeń nie są szyfrowane. Szyfrowany jest wyłącznie payload. Dobieraj nazwy kanałów tak, aby nie ujawniały tego, co chronisz.
- Krawędź Realtime nie może analizować payloadów. Nazwy kanałów i zdarzeń pozostają widoczne, natomiast payload jest zaszyfrowany.
Następne kroki
- Autoryzacja kanałów to mechanizm podpisów, na którym opiera się ten przewodnik.
- Publikowanie zdarzeń opisuje same API publikacji i batchów.
- Kanały cache wyjaśniają odtwarzanie ostatniego zdarzenia, z którym łączy się private-encrypted-cache-.
Powiązane zasoby
Kontynuuj z dokumentacją, przewodnikami i przykładami dotyczącymi tego tematu. Zasoby są w języku angielskim.
Poznaj możliwościRealtimePodążaj ścieżką naukiBuild your first integrationPrzewodnik wdrożeniowySend your first realtime event
Wypróbuj ćwiczenie i uzyskaj brief wdrożeniowy