[JAVA] Schützen Sie mit Cloud-Funktionen erstellte REST-APIs mit Firebase-Authentifizierung. Wenden Sie die Firebase-Authentifizierung auf die API Ihres eigenen REST-Servers an.

Letztes Mal namens Cloud Functions for Firebase, eine Logik, die über HTTPS in einer Umgebung ohne Server platziert wird.

In Bezug auf den HTTPS-Aufruf wird nun die Funktion functions.https.onRequest verwendet, die zu diesem Zeitpunkt verwendet wurde.

index.ts


export const echo = functions.https.onRequest((request, response) => {
  const task = request.body
  response.send(JSON.stringify(task))
})

Abgesehen davon scheint es eine Methode namens functions.https.onCall zu geben, und dieses Mal werde ich diese Methode verwenden. Lassen Sie uns als Ausgangspunkt überlegen, wie Sie die auf Funktionen basierende REST-API mit der Authentifizierungsfunktion von Firebase schützen können.

Übrigens möchte ich die Firebase-Authentifizierung in den von mir selbst erstellten REST-Server integrieren.

TL;DR

Wenn Sie einen REST-Service mit functions.https.onRequest erstellen, wird die Authentifizierungsprüfung usw. nicht automatisch durchgeführt. Daher ist es erforderlich, dies zu tun. Wenn Sie jedoch das Firebase-ID-Token in den Authorization-Header einfügen, kann es nur von Firebase-authentifizierten Benutzern verwendet werden.

Da das ID-Token im JWT-Format gesendet wird, das auf Manipulationen überprüft werden kann, kann die Funktionsseite außerdem die Gültigkeit überprüfen, indem sie sich bei Firebase erkundigt, und die von Firebase nummerierte Benutzer-ID kann abgerufen werden.

Sie können Firebase die Gültigkeit des ID-Tokens überprüfen lassen, indem Sie das SDK für den selbst erstellten REST-Server einfügen. Auch wenn Sie das SDK nicht einfügen können, überprüfen Sie die Gültigkeit mit der sogenannten JWT-Token-Bibliothek usw. Ist möglich. Ich habe sowohl SDK als auch mich selbst ausprobiert, aber ich dachte, es wäre einfacher, eine Bibliothek vorzubereiten und selbst zu überprüfen, wenn die Einstellung der Umgebung in SDK ziemlich schwierig ist.

In der Abbildung sieht es so aus.

image.png

image.png

Ich habe die WEB-Anwendung diesmal nicht auf dem Server bereitgestellt, aber ich habe sie unter der Annahme geschrieben, dass sie auf Firebase Hosting bereitgestellt wird. Auch die Notwendigkeit der Unterstützung von CORS (Cross-Origin Resource Sharing) wird in diesem Artikel nicht richtig beschrieben, aber onCall scheint CORS-kompatibel zu sein.

Versuchen Sie es zunächst ohne Firebase-Authentifizierung

Verwenden wir nun functions.https.onCall. Ich möchte dem vorherigen Projekt Funktionen hinzufügen, damit ich das vorherige Projekt von Github erhalte.

$ git clone --branch restsamples000 https://github.com/masatomix/fb_function_samples.git
$ cd fb_function_samples/functions/
$ npm install
$ cd ../

$ firebase use --add   //Wählen Sie Ihr Projekt unter

Lassen Sie uns nun bauen, indem wir index.ts Folgendes hinzufügen.

index.ts


export const echo_onCall = functions.https.onCall((data, context) => {
  console.log('data: ' + JSON.stringify(data))
  console.log('context.auth: ' + JSON.stringify(context.auth))
  if (context.auth) {
    console.log('context.auth.uid: ' + context.auth.uid)
  }
  return data
})

Bauen:

$ firebase deploy --only functions

Rufen Sie mit Curl über die Befehlszeile an

Überprüfen Sie zunächst. Das ursprüngliche const hello = functions.https.onRequest ((req, res) ... könnte mit der folgenden Methode aufgerufen werden.

$ cat request.json
{
  "id": "001",
  "name": "Hallo",
  "isDone": true
}

$ curl -X POST -H "Content-Type:application/json" \
  --data-binary  @request.json \
  https://us-central1-xxxxx-xxxxxxx.cloudfunctions.net/echo | jq

{
  "id": "001",
  "name": "Hallo",
  "isDone": true
}
$ (Normalerweise wiedergegeben)

Übrigens, const echo_onCall = functions.https.onCall ((Daten, Kontext) ... Hierfür scheint das Format des Aufrufs bis zu einem gewissen Grad festgelegt zu sein.

Detail ist

Sie können es von curl wie folgt aufrufen.

$ cat post_data.json
{
  "data": { //Wickeln Sie es in Daten,
    "id": "001",
    "name": "Hallo",
    "isDone": true
  }
}




$ curl -X POST -H "Content-Type:application/json" \
  --data-binary @post_data.json \
  https://us-central1-xxxxx-xxxxxxx..cloudfunctions.net/echo_onCall | jq

{
  "result": { //Im Ergebnis verpackt und zurückgegeben
    "id": "001",
    "name": "Hallo",
    "isDone": true
  }
}

Die neu registrierte Funktion gibt einfach die Argumentdaten zurück, aber von den POST-Daten wird der in die data-Eigenschaft eingeschlossene Teil extrahiert, in der result-Eigenschaft festgelegt und zurückgegeben.

Nun, es ist schwer zu verstehen, aber ich verstehe, wie es funktioniert. ..

Erstellen Sie ein Vue.js-Projekt und versuchen Sie, es über das SDK aufzurufen

Übrigens functions.https.onCall ((Daten, Kontext) ..., aber wenn diese Funktion aus dem WEB aufgerufen wird, scheint es, dass sie aus dem Firebase SDK (Firebase JavaScript SDK usw.) aufgerufen werden soll. Richtig.

Als nächstes werde ich es über SDK mit der mit Vue.js erstellten Anwendung aufrufen.

Hinweise zur ungefähren Verwendung der Hauptfunktionen von Vue.js (Firebase-Authentifizierung / Autorisierung) ToDo mit Firebase-Authentifizierung Ich habe eine App, also werde ich sie verwenden. Aktivieren Sie außerdem die Authentifizierung usw. auf der Firebase-Seite gemäß diesem Artikel. Ich werde die Authentifizierung später verwenden.

Klicken Sie hier, um eine Vue.js-App zu erstellen.

$ git clone --branch idToken001 https://github.com/masatomix/todo-examples.git
$ cd todo-examples/
$ npm install

src/firebaseConfig.Schreiben Sie js in Ihre eigenen Einstellungen um

$ npm run dev

Versuchen Sie nach dem Start des Servers, auf http: // localhost: 8080 / # / token / zuzugreifen. image.png

Da dieser Bildschirm nicht durch die Firebase-Authentifizierung geschützt ist, wird der WEB-Bildschirm mit der Schaltfläche angezeigt. Klicken Sie auf diese Schaltfläche. image.png

Einige Daten wurden zurückgegeben.

Diese Quelle ist übrigens wie folgt.

Token.vue


<template>
  <main class="container">
    <div>HTTP-Aufruf!</div>
    <button class="btn btn-primary" @click="checkToken">HTTP-Aufruf!</button>
  </main>
</template>

<script>
import firebase from "firebase";

export default {
  name: "Token",
  methods: {
    checkToken() {
      if (firebase.auth().currentUser) {
        firebase.auth().currentUser.getIdToken()
          .then(token => console.log(token));
      } else {
        console.log("currentUser is null");
      }

      const value = {
        id: "001",
        name: "Hallo",
        isDone: true
      };
      //HTTP-Aufruf
      const echo_onCall = firebase.functions().httpsCallable("echo_onCall");
      echo_onCall(value).then(result => alert(JSON.stringify(result)));
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
  

Die in den Funktionen von Firebase registrierte Funktion echo_onCall sollte nur das Argument zurückgeben, aber wenn die Schaltfläche gedrückt wird, wird die Funktion const echo_onCall = firebase.functions (). HttpsCallable ("echo_onCall") des SDK , echo_onCall (value) Es scheint, dass der übergebene Wert in die Dateneigenschaft des Rückrufs eingeschlossen und zurückgegeben wird.

Schauen wir uns die aufsteigenden und absteigenden Telegramme mit einem Browser an.

Klettertelegramm image.png

Abwärts-Telegramm image.png

Das SDK zeigt {'data': value} auf dem Bildschirm an, aber Sie können sehen, dass das, was den von curl gesendeten und empfangenen Daten entspricht, tatsächlich zurückgegeben wird.

Mit dem oben Gesagten wurde bestätigt, dass das Nachrichtenformat des vorherigen Curl-Beispiels der Methode zum Aufrufen des SDK entspricht.

Versuchen Sie es anschließend mit der Firebase-Authentifizierung

Ab diesem Punkt werden wir schrittweise über die Authentifizierung sprechen.

Versuchen Sie als Nächstes, auf http: // localhost: 8080 / # / token_auth zuzugreifen. Dieser Bildschirm wurde zum Schutz mit Firebase-Authentifizierung erstellt E3% 82% A2% E3% 82% AF% E3% 82% BB% E3% 82% B9% E3% 81% 97% E3% 81% 9F% E3% 81% A8% E3% 81% 8D% E3% 81% AB% E3% 83% AD% E3% 82% B0% E3% 82% A4% E3% 83% B3% E6% B8% 88% E3% 81% BF% E3% 81% A7% E3% 81% AA% E3% 81% 84% E5% A0% B4% E5% 90% 88% E3% 81% AF% E3% 83% AD% E3% 82% B0% E3% 82% A4% E3% 83% B3% E7% 94% BB% E9% 9D% A2% E3% 82% 92% E8% A1% A8% E7% A4% BA% E3% 81% 97% E8% AA% 8D% E8% A8% BC% E3% 81% 97% E3% 81% 9F% E3% 82% 89% E8% A9% B2% E5% BD% 93% E7% 94% BB% E9% 9D% A2% E3% 81% B8% E9% 81% B7% E7% A7% BB% E3% 81% 99% E3% 82% 8B), sodass Sie zum Anmeldebildschirm weitergeleitet werden. image.png Es gibt einen Link zum Anmelden mit Ihrem Google-Konto und zur ID / Pass-Authentifizierung. Versuchen Sie daher, sich in Ihrer Firebase-Umgebung entsprechend anzumelden.

Wenn Sie angemeldet sind, gelangen Sie zum selben Bildschirm (Token.vue) wie der mit der Schaltfläche.

Wenn Sie auf die Schaltfläche klicken und die Funktion Funktionen über das SDK aufrufen, erhalten Sie das gleiche Ergebnis. Die Meldung unterscheidet sich jedoch zwischen der Authentifizierung von Firebase und der Nicht-Authentifizierung von Firebase. Insbesondere wenn Firebase authentifiziert ist, im HTTP-Anforderungsheader

Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyZTQ2MGZmM2EzZDQ2ZGZlYzcyNGQ4NDg0ZjczNDc2YzEzZTIwY2YiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb....

Das Bearer-Token wird im Authorization-Header festgelegt. image.png

Mit anderen Worten: ** Ich habe festgestellt, dass beim Aufrufen der HTTPS-Funktion des Servers mithilfe des Firebase-SDK das Token im Autorisierungsheader festgelegt wird, wenn Firebase authentifiziert wird **.

Übrigens ist dieses Token

firebase.auth().currentUser.getIdToken().then(token => console.log(`Bearer ${token}`));

Der Wert des Tokens.

Token-Wert-Beispiel

Ein Beispiel für den tatsächlichen Wert sieht so aus.

eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyZTQ2MGZmM2EzZDQ2ZGZlYzcyNGQ4NDg0ZjczNDc2YzEzZTIwY2YiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20veHh4eHh4LXNhbXBsZXMiLCJhdWQiOiJ4eHh4eHgtc2FtcGxlcyIsImF1dGhfdGltZSI6MTU0OTU5MzgxMCwidXNlcl9pZCI6IlVOWHBENDBZNUVZZkt4eHh4eHh4Iiwic3ViIjoiVU5YcEQ0MFk1RVlmS3h4eHh4eHgiLCJpYXQiOjE1NDk1OTM4MTAsImV4cCI6MTU0OTU5NzQxMCwiZW1haWwiOiJob2dlaG9nZUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJob2dlaG9nZUBleGFtcGxlLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19Cg.D3ZDm-UPDGRqezz6Q9QKpZtSVovxlWZNt-pyArLlruo1DqfkmaYkXuTjMpxIbB0sCySDAme3ZeenRYxgBrQJhqZiwFx_mTNfjNoQSUVbRjLrSYdtXLBtgy6OvJGsN93UfFfhb2kAeBjDtOPTE6WOWyJ7wDRK0bmkYvYLZ9NMgFsc9-ELfqew7jOVnZTsem3dwkhfQ-_qHJnRD7xkLmEu2CA0yUSbajVwy-rDpC5eRVZVjnnFpgghJckBpQTdxXesM58aRF5uiSLsIi6KYimDyqV_cQL_oAojW0fR-X-Q0GqD4FYsGmk1hMy-n5ClOUmCKvHLcN6tAWQKScdvYAx3cA

Wenn Sie genau hinschauen, wird es durch Punkte in drei Teile geteilt.

Und wenn ich versuche, base64 zu dekodieren, werden die ersten beiden an der durch Punkte getrennten Stelle wie folgt getrennt:

(Nur hier trifft man mit Linux. Es kann sich geringfügig von den Optionen unterscheiden)


$ echo eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyZTQ2MGZmM2EzZDQ2ZGZlYzcyNGQ4NDg0ZjczNDc2YzEzZTIwY2YiLCJ0eXAiOiJKV1QifQ | base64 -d | jq
{
  "alg": "RS256",
  "kid": "b2e460ff3a3d46dfec724d8484f73476c13e20cf",
  "typ": "JWT"
}

Irgendwie sind JWT und der Schlüsselalgorithmus RS256 geschrieben. Und hier ist der zweite.

(Nur hier trifft man mit Linux. Es kann sich geringfügig von den Optionen unterscheiden)


$ echo eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20veHh4eHh4LXNhbXBsZXMiLCJhdWQiOiJ4eHh4eHgtc2FtcGxlcyIsImF1dGhfdGltZSI6MTU0OTU5MzgxMCwidXNlcl9pZCI6IlVOWHBENDBZNUVZZkt4eHh4eHh4Iiwic3ViIjoiVU5YcEQ0MFk1RVlmS3h4eHh4eHgiLCJpYXQiOjE1NDk1OTM4MTAsImV4cCI6MTU0OTU5NzQxMCwiZW1haWwiOiJob2dlaG9nZUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJob2dlaG9nZUBleGFtcGxlLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19Cg | base64 -d | jq
{
  "iss": "https://securetoken.google.com/xxxxxx-samples",
  "aud": "xxxxxx-samples",
  "auth_time": 1549593810,
  "user_id": "UNXpD40Y5EYfKxxxxxxx",
  "sub": "UNXpD40Y5EYfKxxxxxxx",
  "iat": 1549593810,
  "exp": 1549597410,
  "email": "[email protected]",
  "email_verified": false,
  "firebase": {
    "identities": {
      "email": [
        "[email protected]"
      ]
    },
    "sign_in_provider": "password"
  }
}

Sie können sehen, dass eine Art von Authentifizierungsinformationen enthalten ist. Der dritte Block sind Signaturdaten, aber ich werde sie später schreiben, also gehen wir weiter. ..

functions.https.onCall überprüft das Token

Übrigens, wenn die mit const echo_onCall = functions.https.onCall ((Daten, Kontext) ... registrierte Funktion über SDK aufgerufen wird, wird bei Authentifizierung von Firebase eine Anfrage mit einem Bearer-Token gesendet. Und es stellt sich heraus, dass diese "onCall" -Funktion funktioniert, aber wenn sie eine Anfrage mit einem Bearer-Token empfängt, scheint sie auch dieses Token automatisch zu verifizieren.

In der Tat, wenn ich das Token entsprechend umschreibe und es mit Curl anfordere, ...

$ curl -X POST -H "Content-Type:application/json" \
   --data-binary @post_data.json \
   -H 'Authorization:Träger geeignet' \
   https://us-central1-xxxxx-xxxxxxx..cloudfunctions.net/echo_onCall  | jq

  {
  "error": {
    "status": "UNAUTHENTICATED",
    "message": "Unauthenticated"
  }
}

Als ich den Authorization-Header hinzufügte, stellte ich fest, dass er die Gültigkeit des Tokens überprüft. Fordern wir vorerst sogar das richtige Token an.

$ curl -X POST -H "Content-Type:application/json" \
   --data-binary @post_data.json \
   -H 'Authorization:Träger Der richtige Typ, der gerade zurückgekommen ist' \
   https://us-central1-xxxxx-xxxxxxx..cloudfunctions.net/echo_onCall

{"result":{"id":"001","name":"Hallo","isDone":true}}
$

Wenn Sie das richtige Token anhängen, erhalten Sie das gleiche Ergebnis, als hätten Sie den Authorization-Header nicht.

functions.https.onRequest überprüft das Token nicht

Als nächstes kommt functions.https.onRequest, aber das ist

$ curl -X POST  -H "Content-Type:application/json"   \
 -H 'Authorization:Träger geeignet' \
 --data-binary  @request.json \
 https://us-central1-xxxxx-xxxxxxx..cloudfunctions.net/echo


{
  "id": "001",
  "name": "Hallo",
  "isDone": true
}
$ (Ich wurde normalerweise wiederholt..)

Selbst wenn ich ein geeignetes Token gesetzt habe, konnte ich den Wert erhalten. Ich habe festgestellt, dass functions.https.onRequest den Authorization-Header nicht automatisch überprüft.

Einmal zusammengefasst

Aus dem Obigen geht hervor, dass es wie folgt organisiert werden kann.

REST erstellt mit functions.https.onCall

Dadurch wird automatisch die Gültigkeit des Bearer-Tokens im Authorization-Header überprüft

firebase.auth().currentUser.getIdToken().then(token => console.log(token));

Wenn Sie das Token mit erhalten, setzen Sie es in den Authorization-Header und werfen Sie es. Sie können die Funktion nur von dem Benutzer verwenden, der Firebase authentifiziert hat (obwohl es erforderlich ist, es als Fehler zu verarbeiten, wenn der Authorization-Header überhaupt nicht angehängt ist). Die Nachrichtenspezifikationen von Anforderung und Antwort sind jedoch bis zu einem gewissen Grad festgelegt.

Es scheint, dass es organisiert werden kann. Insbesondere wenn es vom Firebase SDK aufgerufen werden soll, setzt das SDK das Token automatisch im Authorization-Header, was einfacher und einfacher ist.

REST erstellt mit functions.https.onRequest

Dies überprüft nicht automatisch die Gültigkeit des Bearer-Tokens im Authorization-Header, also zuerst vom Client (WEB-Anwendung oder Curl).

firebase.auth().currentUser.getIdToken().then(token => console.log(token));

Holen Sie sich das Token mit und legen Sie es im Authorization-Header fest, damit es verbunden werden kann. Und die serverseitige Funktion ** fragt Firebase nach der Gültigkeit des Tokens ** ab, sodass anscheinend nur Benutzer mit Firebase-Authentifizierung die Funktion verwenden können. (Wenn der Autorisierungsheader überhaupt nicht angehängt ist, müssen Sie einen Fehler machen). Die Nachrichtenspezifikationen für Anforderung und Antwort sind ebenfalls optional.

Es scheint, dass es organisiert werden kann. Dies scheint vielseitiger zu sein, wenn ein normalerweise REST-Server für Benutzer mit Firebase-Authentifizierung bereitgestellt wird.

Eine in Funktionen registrierte Funktion, die Firebase nach der Gültigkeit des Tokens fragt.

Der Rest besteht nun darin, Firebase mit der Funktion functions.https.onRequest auf der Serverseite nach der Gültigkeit des Tokens abzufragen. Definieren Sie beispielsweise eine Funktion, um den Wert des Authorization-Headers der Anforderung abzurufen, wie unten gezeigt.

index.ts(getIdToken)


function getIdToken(request, response) {
  if (!request.headers.authorization) {
    throw new Error('Autorisierungsheader existiert nicht.')
  }
  const match = request.headers.authorization.match(/^Bearer (.*)$/)
  if (match) {
    const idToken = match[1]
    return idToken
  }
  throw new Error(
    'Bearer-Token konnte nicht aus dem Authorization-Header abgerufen werden.',
  )
}

Rufen Sie die Funktion mit functions.https.onRequest auf, um das ID-Token abzurufen, und überprüfen Sie mit der bereitgestellten verifyIdToken -Methode die Gültigkeit des ID-Tokens.

javascript:index.ts(functions.https.onRequest)


import * as corsLib from 'cors'
const cors = corsLib()

export const echo = functions.https.onRequest((request, response) => {
  return cors(request, response, async () => {
    const task = request.body
    console.log(JSON.stringify(task))

    try {
      const idToken = getIdToken(request, response) //Überprüfen Sie, ob Sie Inhaber-Token erhalten können
      const decodedToken = await admin.auth().verifyIdToken(idToken)

      console.log(decodedToken.uid) //Benutzer-UID bei der Firebase-Authentifizierung
      response.send(JSON.stringify(task))
    } catch (error) {
      console.log(error.message)
      response.status(401).send(error.message)
    }
  })
})

Stellen Sie es für Funktionen bereit, rufen Sie die Funktion von curl usw. mit verschiedenen Werten des ID-Tokens auf und überprüfen Sie den Vorgang. Sie können sehen, dass die JSON-Daten nur zurückgegeben werden, wenn das ID-Token auf dem Bearer im Authorization-Header den richtigen Wert hat.

Was ist das überhaupt? Und über Manipulationen und Verschlüsselung

Ich habe den Wert des ID-Token-Beispiels bereits früher gezeigt, aber dieses ID-Token wird häufig in der Welt der Authentifizierungsinfrastruktur wie OpenID Connect JWT (JSON-Web-Token) verwendet. ) Wird genutzt. Ich werde die Details jemandem überlassen, der damit vertraut ist :-) JWT ist beim Senden von JSON-Daten an das Netzwerk.

Header part.Payload (JSON-Body) .Signature (Signatur)

Es ist ein Mechanismus zum Senden in einem durch Punkte geteilten Format (jeder Teil ist Base64-codiert). Die Signatur sind die Daten, die den Header-Abschnitt und den Payload-Abschnitt mit der im Header-Abschnitt beschriebenen Signaturmethode signiert haben (normalerweise wird sie häufig mit dem privaten Schlüssel des Erstellers signiert). Daher wird der Header mit dem öffentlichen Schlüssel des Erstellers signiert. Sie können die Manipulation von / Payload überprüfen.

Mit anderen Worten, ich werde es zum Beispiel als nächstes tun, aber ** Wenn Sie die Firebase-Authentifizierung als Authentifizierungs- / Autorisierungsbasis Ihres eigenen REST-Servers verwenden möchten ** Durch Empfangen dieses ID-Tokens das Firebase-System, das der Aussteller des ID-Tokens ist Es ist möglich, den öffentlichen Schlüssel von JWT-Daten abzurufen und deren Gültigkeit zu überprüfen.

Darüber hinaus kann die Firebase-Benutzer-ID endgültig aus dem ID-Token abgerufen werden. Da die Manipulation jedoch erkannt werden kann, werden auch Spoofing und Datenmanipulation verhindert.

Übrigens in den JSON-Daten des Abschnitts Payload

{
  "iss": "https://securetoken.google.com/xxxxxx-samples",
  "aud": "xxxxxx-samples",
  "auth_time": 1549593810,
  "user_id": "UNXpD40Y5EYfKxxxxxxx",
  "sub": "UNXpD40Y5EYfKxxxxxxx", //Firebase-Benutzer-ID
  "iat": 1549593810,
  "exp": 1549597410,  //  <- Fri Feb 08 12:43:30 JST 2019 Ablaufdatum
  "email": "[email protected]",
  "email_verified": false,
  "firebase": {
    "identities": {
      "email": [
        "[email protected]"
      ]
    },
    "sign_in_provider": "password"
  }
}

Da das Ablaufdatum so angegeben ist, können Sie überprüfen, ob die Anforderung gültig und authentifiziert ist, indem Sie dieses Datum und diese Uhrzeit überprüfen.

JWT besteht jedoch nur aus Base64-codierten Daten und wird daher nicht verschlüsselt. Wenn Sie den Inhalt nicht sehen möchten, müssen Sie SSL daher ordnungsgemäß übergeben. Nun, es ist okay, weil REST mit SSL erstellt wird. ..

Ich möchte die Gültigkeit von Token von meinem eigenen REST-Server aus überprüfen, nicht von Funktionen

Nun, ich sagte, ich würde es als nächstes tun, aber ** ich werde die Firebase-Authentifizierung als Authentifizierungs- / Autorisierungsplattform für meinen eigenen REST-Server verwenden . Insbesondere habe ich die Gültigkeit des ID-Tokens anhand der zuvor in den Funktionen von Firebase registrierten Funktion überprüft. Diesmal habe ich jedoch die Gültigkeit des ID-Tokens des von mir selbst erstellten REST-Servers überprüft ( durch Überprüfen der Signatur). Die Überprüfung der Datenmanipulation und die Bestätigung des Ablaufdatums **) werden durchgeführt.

Übrigens genau genommen

Auf dieser Seite wird ausführlich erklärt, dass Sie dies überprüfen sollten.

Erstellen Sie Ihren eigenen REST-Server

Aus diesem Grund erstellen wir einen REST-Server in einer Umgebung, die nichts mit Funktionen oder Firebase zu tun hat.

Wir haben bereits einen Java-Server erstellt und festgeschrieben, der unter http: // localhost: 8081 / echo gestartet wird. Lassen Sie ihn also fallen und erstellen Sie eine Umgebung.

Ah ... Sie müssen Apache Maven installieren, um den Server zu erstellen und zu starten, aber ich werde das Maven-Setup-Verfahren weglassen. .. ..

$ git clone --branch 0.0.1 https://github.com/masatomix/spring-boot-sample-tomcat.git 
$ cd spring-boot-sample-tomcat
$ mvn eclipse:clean eclipse:eclipse
$ mvn spring-boot:run

Sollte mit beginnen.

Anruf von Curl

Dieser REST-Server ist von Curl

$ cat post_data.json
{
    "id": "001",
    "name": "Hallo",
    "isDone": true
}:

$ curl -X POST -H "Content-Type:application/json" \
  --data-binary @post_data.json \
  -H 'Authorization: Bearer eyJhbGciOiJSUzI1xxxxxxxxxx'\
  http://localhost:8081/echo

Sie können es mit nennen. Versuchen Sie wie gewohnt, das ID-Token mit den Entwicklungstools Ihres Browsers abzurufen.

image.png

Hinzufügen eines WEB-Bildschirms zum Aufrufen von REST

Wenn Sie beim Aufrufen über den WEB-Bildschirm die Token.vue des mit Vue.js erstellten Projekts durch die folgende ersetzen, wird eine weitere Schaltfläche angezeigt. Sie können eine REST-Anfrage senden, indem Sie diese Taste drücken. Überprüfen Sie sie daher bitte. Dieser Code ist ein Beispielcode, der beim Ausführen eines REST-Aufrufs ein ID-Token an den Authorization-Header übergibt.

Token.vue


<template>
  <main class="container">
    <div>HTTP-Aufruf!</div>
    <button class="btn btn-primary" @click="checkToken">HTTP-Aufruf!</button>

    <hr>
    <div>HTTP-Aufruf!</div>
    <button class="btn btn-primary" @click="checkToken_without_sdk">HTTP-Aufruf(Eigener Server)!</button>
  </main>
</template>

<script>
import firebase from "firebase";
import axios from "axios";

export default {
  name: "Token",
  methods: {
    checkToken() {
      if (firebase.auth().currentUser) {
        firebase.auth().currentUser.getIdToken()
          .then(token => console.log(token));
      } else {
        console.log("currentUser is null");
      }

      const value = {
        id: "001",
        name: "Hallo",
        isDone: true
      };
      //HTTP-Aufruf
      const echo_onCall = firebase.functions().httpsCallable("echo_onCall");
      echo_onCall(value).then(result => alert(JSON.stringify(result)));
    },

    checkToken_without_sdk() {
      if (firebase.auth().currentUser) {
        firebase.auth().currentUser.getIdToken()
          .then(token => {
            console.log(token);

            const value = {
              id: "001",
              name: "Hallo",
              isDone: true
            };
            const config = {
              url: "http://localhost:8081/echo",
              method: "POST",
              headers: {
                "Content-type": "application/json",
                Authorization: `Bearer ${token}`
              },
              data: value,
              json: true
            };
            axios(config)
              .then(response => alert(JSON.stringify(response.data)))
              .catch(error => alert(error.message));
          });
      } else {
        console.log("currentUser is null");
      }
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

image.png

Code, der den REST-Server besitzt, überprüft die Gültigkeit von ID-Token

Schließlich der Java-Code für den REST-Server. Die Gültigkeit des ID-Tokens wird mithilfe der JWT-Bibliothek Nimbus JOSE + JWT überprüft.

SampleController.java


package nu.mine.kino.web;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import nu.mine.kino.service.Hello;

@Controller
@RequestMapping("/echo")
public class SampleController {
    private static final Pattern CHALLENGE_PATTERN = Pattern
            .compile("^Bearer *([^ ]+) *$", Pattern.CASE_INSENSITIVE);

    @ResponseBody
    @CrossOrigin
    @RequestMapping(produces = "application/json; charset=utf-8", method = RequestMethod.POST)
    public Hello helloWorld(
            @RequestHeader(value = "Authorization", required = true) String authorization,
            @RequestBody Hello hello) throws UNAUTHORIZED_Exception {

        Matcher matcher = CHALLENGE_PATTERN.matcher(authorization);
        if (matcher.matches()) {
            String id_token = matcher.group(1);
            if (JWTUtils.checkIdToken(id_token)) {
                return hello;
            } else {
                throw new UNAUTHORIZED_Exception("ID Token is invalid.");
            }
        }
        throw new UNAUTHORIZED_Exception(
                "Bearer-Token konnte nicht aus dem Authorization-Header abgerufen werden.");
    }

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    class UNAUTHORIZED_Exception extends Exception {
        private static final long serialVersionUID = -6715447914841144335L;

        public UNAUTHORIZED_Exception(String message) {
            super(message);
        }
    }

    // private void snippet() throws IOException, FirebaseAuthException {
    // String id_token = "";
    // ClassLoader loader = Thread.currentThread().getContextClassLoader();
    // //Setzen Sie es in Ressourcen
    // InputStream serviceAccount = loader.getResourceAsStream(
    // "xxxxxxxx-firebase-adminsdk-xxxxxxxxx.json");
    // FirebaseOptions options = new FirebaseOptions.Builder()
    // .setCredentials(GoogleCredentials.fromStream(serviceAccount))
    // .setDatabaseUrl("https://xxxxxxxx.firebaseio.com").build();
    // FirebaseApp.initializeApp(options);
    //
    // FirebaseToken decodedToken = FirebaseAuth.getInstance()
    // .verifyIdToken(id_token);
    // String uid = decodedToken.getUid();
    //
    // }
}

Der von if (JWTUtils.checkIdToken (id_token)) .. aufgerufene Code lautet wie folgt.

JWTUtils.java


package nu.mine.kino.web;

import java.io.IOException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.X509CertUtils;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Masatomi KINO
 * @version $Revision$
 */
@Slf4j
public class JWTUtils {
    public static boolean checkIdToken(String id_token) {
        try {
            SignedJWT decodeObject = SignedJWT.parse(id_token);
            log.debug("Header : " + decodeObject.getHeader());
            log.debug("Payload: " + decodeObject.getPayload());
            log.debug("Sign   : " + decodeObject.getSignature());

            JWSAlgorithm algorithm = decodeObject.getHeader().getAlgorithm();
            JWTClaimsSet set = decodeObject.getJWTClaimsSet();
            log.debug("Algorithm: {}", algorithm.getName());
            log.debug("ExpirationTime(Haltbarkeitsdatum): {}", set.getExpirationTime());
            log.debug("IssueTime(Ausstellungsdatum und -zeit): {}", set.getIssueTime());
            log.debug("Subject(key): {}", set.getSubject());
            log.debug("Issuer(Herausgeber): {}", set.getIssuer());
            log.debug("Audience: {}", set.getAudience());
            log.debug("Nonce: {}", set.getClaim("nonce"));
            log.debug("auth_time(Datum und Uhrzeit der Authentifizierung): {}", set.getDateClaim("auth_time"));
            log.debug("Schlüsselalgorithmus({})", algorithm.getName());
            boolean isExpired = new Date().after(set.getExpirationTime());
            log.debug("now after ExpirationTime?: {}", isExpired);
            if (isExpired) {
                log.warn("Es ist abgelaufen.");
                return false;
            }
            String jwks_uri = "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]";
            return checkRSSignature(decodeObject, jwks_uri);

        } catch (ParseException e) {
            log.warn("Fehler beim Abrufen des öffentlichen Schlüssels des Servers.{}", e.getMessage());
        } catch (IOException e) {
            log.warn("Fehler beim Abrufen des öffentlichen Schlüssels des Servers.{}", e.getMessage());
        } catch (JOSEException e) {
            log.warn("Überprüfen Sie, ob die Verarbeitung fehlgeschlagen ist.{}", e.getMessage());
        }
        return false;
    }

    private static boolean checkRSSignature(SignedJWT decodeObject,
            String jwks_uri) throws JOSEException, IOException, ParseException {
        //Holen Sie sich die KeyID aus dem Header
        String keyID = decodeObject.getHeader().getKeyID();
        log.debug("KeyID: {}", keyID);

        Map<String, Object> resource = getResource(jwks_uri);
        String object = resource.get(keyID).toString();
        X509Certificate cert = X509CertUtils.parse(object);
        RSAKey rsaKey = RSAKey.parse(cert);

        JWSVerifier verifier = new RSASSAVerifier(rsaKey);
        boolean verify = decodeObject.verify(verifier);
        log.debug("valid?: {}", verify);
        return verify;
    }

    private static Map<String, Object> getResource(String target)
            throws IOException {
        Client client = ClientBuilder.newClient();
        Response restResponse = client.target(target)
                .request(MediaType.APPLICATION_JSON_TYPE).get();
        String result = restResponse.readEntity(String.class);
        log.debug(result);
        return json2Map(result);
    }

    private static Map<String, Object> json2Map(String result)
            throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(result,
                new TypeReference<Map<String, Object>>() {
                });
    }
}

Der Java-Code ist lang und humorvoll, aber fast so. ..

--SampleController # helloWorld wird aufgerufen, wenn auf http: // localhost: 8081 / echo zugegriffen wird

Damit ist die Gültigkeitsprüfung des ID-Tokens abgeschlossen!

Dieses Mal habe ich das ID-Token selbst in Java überprüft, aber die Hauptsprache ist das von Firebase bereitgestellte SDK.

Ich konnte es aufgrund des Java SDK initialisieren und auf dem REST-Server verwenden (ich habe den Code auch als Kommentar hinterlassen), aber "Erstellen eines privaten Schlüssels auf dem Bildschirm" Dienstkonto "" oder "Dienstkonto" Ich benötige eine Einstellungsdatei mit Authentifizierungsinformationen "usw., daher überspringe ich den Vorgang.

Ah, ich war müde.

Zusammenfassung

Die Authentifizierungsinformationen bei der Authentifizierung mit Firebase hatten ein Format namens ID-Token. Hierbei handelt es sich um eine Spezifikation namens JWT, die auf Manipulationen überprüft werden kann. Wenn Sie also die auf dem REST-Server veröffentlichte API unabhängig von der Implementierungsmethode (Firebase-Funktionen, eigener REST-Server usw.) durch Authentifizierung schützen, lassen Sie das ID-Token zum Zeitpunkt des API-Aufrufs senden. Also habe ich herausgefunden, dass ich überprüfen kann, ob ich ein legitimer Firebase-angemeldeter Benutzer bin.

Da das ID-Token die Benutzer-ID (JWT's sub'property) enthält, die der Schlüsselwert für die Firebase-Authentifizierung ist, verwenden Sie diesen Wert nach Überprüfung des ID-Tokens, um ihn mit den Benutzerdaten in Ihrer eigenen Datenbank zu verknüpfen. Es scheint, dass Sie es schaffen können.

das ist alles. Danke für deine Arbeit. .. ..

Verwandte Links (Quelle)

Die Quelle des Projekts, das nach dem Hinzufügen des Codes für Funktionen bereitgestellt werden soll.

$ git clone --branch restsamples001 https://github.com/masatomix/fb_function_samples.git
$ cd fb_function_samples/functions/
$ npm install
$ cd ../

$ firebase use --add   //Wählen Sie Ihr Projekt unter
$ firebase deploy --only functions

Quelle der mit Vue.js erstellten WEB-Anwendung, nachdem das Hinzufügen des Codes abgeschlossen ist

$ git clone --branch idToken002 https://github.com/masatomix/todo-examples.git
$ cd todo-examples/
$ npm install

src/firebaseConfig.Schreiben Sie js in Ihre eigenen Einstellungen um

$ npm run dev

Beispielquelle eines eigenen in Java erstellten REST-Servers

$ git clone --branch 0.0.1 https://github.com/masatomix/spring-boot-sample-tomcat.git 
$ cd spring-boot-sample-tomcat
$ mvn eclipse:clean eclipse:eclipse
$ mvn spring-boot:run

ähnliche Links

Recommended Posts

Schützen Sie mit Cloud-Funktionen erstellte REST-APIs mit Firebase-Authentifizierung. Wenden Sie die Firebase-Authentifizierung auf die API Ihres eigenen REST-Servers an.
Wenden Sie Ihre eigene Domain auf Rails of VPS an und machen Sie sie zu SSL (https) (CentOS 8.2 / Nginx).
Was ich mit der Redmine REST API süchtig gemacht habe
Lesen Sie die Daten der Shizuoka Prefecture Point Cloud DB mit Java und versuchen Sie, die Baumhöhe zu ermitteln.
Ich habe versucht, mit Docker eine Plant UML Server-Umgebung zu erstellen
Ich habe versucht, den Betrieb des gRPC-Servers mit grpcurl zu überprüfen