Google Cloud IoT Core verrà ritirato il 16 agosto 2023. Per saperne di più, contatta il team dedicato al tuo account Google Cloud.

Utilizzo di token web JSON (JWT)

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Per eseguire l'autenticazione su Cloud IoT Core, ogni dispositivo deve preparare un token web JSON (JWT, RFC 7519). I JWT vengono utilizzati per l'autenticazione di breve durata tra i dispositivi e i bridge MQTT o HTTP. In questa pagina vengono descritti i requisiti Cloud IoT Core per i contenuti di JWT.

Cloud IoT Core non richiede un metodo di generazione di token specifico. Una buona raccolta di librerie client helper è disponibile su JWT.io.

Quando crei un client MQTT, il JWT deve essere trasmesso nel campo password del messaggio CONNECT. Quando esegui la connessione tramite HTTP, devi inserire un JWT nell'intestazione di ogni richiesta HTTP.

Creazione di JWT

I JWT sono composti da tre sezioni: un'intestazione, un payload (contenente un set di rivendicazioni) e una firma. L'intestazione e il payload sono oggetti JSON che vengono serializzati in byte UTF-8 e che vengono codificati utilizzando la codifica base64url.

L'intestazione, il payload e la firma di JWT sono concatenati con punti (.). Di conseguenza, un JWT in genere ha il seguente formato:

{Base64url encoded header}.{Base64url encoded payload}.{Base64url encoded signature}

L'esempio seguente illustra come creare un Cloud IoT Core JWT per un determinato progetto. Dopo aver creato il JWT, puoi connetterti al bridge MQTT o HTTP per pubblicare messaggi da un dispositivo.

C++

/**
 * Calculates issued at / expiration times for JWT and places the time, as a
 * Unix timestamp, in the strings passed to the function. The time_size
 * parameter specifies the length of the string allocated for both iat and exp.
 */
static void GetIatExp(char* iat, char* exp, int time_size) {
  // TODO(#72): Use time.google.com for iat
  time_t now_seconds = time(NULL);
  snprintf(iat, time_size, "%lu", now_seconds);
  snprintf(exp, time_size, "%lu", now_seconds + 3600);
  if (TRACE) {
    printf("IAT: %s\n", iat);
    printf("EXP: %s\n", exp);
  }
}

static int GetAlgorithmFromString(const char* algorithm) {
  if (strcmp(algorithm, "RS256") == 0) {
    return JWT_ALG_RS256;
  }
  if (strcmp(algorithm, "ES256") == 0) {
    return JWT_ALG_ES256;
  }
  return -1;
}

/**
 * Calculates a JSON Web Token (JWT) given the path to a EC private key and
 * Google Cloud project ID. Returns the JWT as a string that the caller must
 * free.
 */
static char* CreateJwt(const char* ec_private_path, const char* project_id,
                       const char* algorithm) {
  char iat_time[sizeof(time_t) * 3 + 2];
  char exp_time[sizeof(time_t) * 3 + 2];
  uint8_t* key = NULL;  // Stores the Base64 encoded certificate
  size_t key_len = 0;
  jwt_t* jwt = NULL;
  int ret = 0;
  char* out = NULL;

  // Read private key from file
  FILE* fp = fopen(ec_private_path, "r");
  if (fp == NULL) {
    printf("Could not open file: %s\n", ec_private_path);
    return "";
  }
  fseek(fp, 0L, SEEK_END);
  key_len = ftell(fp);
  fseek(fp, 0L, SEEK_SET);
  key = malloc(sizeof(uint8_t) * (key_len + 1));  // certificate length + \0

  fread(key, 1, key_len, fp);
  key[key_len] = '\0';
  fclose(fp);

  // Get JWT parts
  GetIatExp(iat_time, exp_time, sizeof(iat_time));

  jwt_new(&jwt);

  // Write JWT
  ret = jwt_add_grant(jwt, "iat", iat_time);
  if (ret) {
    printf("Error setting issue timestamp: %d\n", ret);
  }
  ret = jwt_add_grant(jwt, "exp", exp_time);
  if (ret) {
    printf("Error setting expiration: %d\n", ret);
  }
  ret = jwt_add_grant(jwt, "aud", project_id);
  if (ret) {
    printf("Error adding audience: %d\n", ret);
  }
  ret = jwt_set_alg(jwt, GetAlgorithmFromString(algorithm), key, key_len);
  if (ret) {
    printf("Error during set alg: %d\n", ret);
  }
  out = jwt_encode_str(jwt);
  if (!out) {
    perror("Error during token creation:");
  }
  // Print JWT
  if (TRACE) {
    printf("JWT: [%s]\n", out);
  }

  jwt_free(jwt);
  free(key);
  return out;
}

Go


import (
	"errors"
	"io/ioutil"
	"time"

	jwt "github.com/golang-jwt/jwt"
)

// createJWT creates a Cloud IoT Core JWT for the given project id.
// algorithm can be one of ["RS256", "ES256"].
func createJWT(projectID string, privateKeyPath string, algorithm string, expiration time.Duration) (string, error) {
	claims := jwt.StandardClaims{
		Audience:  projectID,
		IssuedAt:  time.Now().Unix(),
		ExpiresAt: time.Now().Add(expiration).Unix(),
	}

	keyBytes, err := ioutil.ReadFile(privateKeyPath)
	if err != nil {
		return "", err
	}

	token := jwt.NewWithClaims(jwt.GetSigningMethod(algorithm), claims)

	switch algorithm {
	case "RS256":
		privKey, _ := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
		return token.SignedString(privKey)
	case "ES256":
		privKey, _ := jwt.ParseECPrivateKeyFromPEM(keyBytes)
		return token.SignedString(privKey)
	}

	return "", errors.New("Cannot find JWT algorithm. Specify 'ES256' or 'RS256'")
}

Java

static MqttCallback mCallback;
static long MINUTES_PER_HOUR = 60;

/** Create a Cloud IoT Core JWT for the given project id, signed with the given RSA key. */
private static String createJwtRsa(String projectId, String privateKeyFile)
    throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
  DateTime now = new DateTime();
  // Create a JWT to authenticate this device. The device will be disconnected after the token
  // expires, and will have to reconnect with a new token. The audience field should always be set
  // to the GCP project id.
  JwtBuilder jwtBuilder =
      Jwts.builder()
          .setIssuedAt(now.toDate())
          .setExpiration(now.plusMinutes(20).toDate())
          .setAudience(projectId);

  byte[] keyBytes = Files.readAllBytes(Paths.get(privateKeyFile));
  PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
  KeyFactory kf = KeyFactory.getInstance("RSA");

  return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
}

Node.js

const createJwt = (projectId, privateKeyFile, algorithm) => {
  // Create a JWT to authenticate this device. The device will be disconnected
  // after the token expires, and will have to reconnect with a new token. The
  // audience field should always be set to the GCP project id.
  const token = {
    iat: parseInt(Date.now() / 1000),
    exp: parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
    aud: projectId,
  };
  const privateKey = readFileSync(privateKeyFile);
  return jwt.sign(token, privateKey, {algorithm: algorithm});
};

Python

def create_jwt(project_id, private_key_file, algorithm):
    """Creates a JWT (https://jwt.io) to establish an MQTT connection.
    Args:
     project_id: The cloud project ID this device belongs to
     private_key_file: A path to a file containing either an RSA256 or
             ES256 private key.
     algorithm: The encryption algorithm to use. Either 'RS256' or 'ES256'
    Returns:
        A JWT generated from the given project_id and private key, which
        expires in 20 minutes. After 20 minutes, your client will be
        disconnected, and a new JWT will have to be generated.
    Raises:
        ValueError: If the private_key_file does not contain a known key.
    """

    token = {
        # The time that the token was issued at
        "iat": datetime.datetime.now(tz=datetime.timezone.utc),
        # The time the token expires.
        "exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(minutes=20),
        # The audience field should always be set to the GCP project id.
        "aud": project_id,
    }

    # Read the private key file.
    with open(private_key_file, "r") as f:
        private_key = f.read()

    print(
        "Creating JWT using {} from private key file {}".format(
            algorithm, private_key_file
        )
    )

    return jwt.encode(token, private_key, algorithm=algorithm)

Intestazione JWT

L'intestazione JWT è costituita da due campi che indicano l'algoritmo di firma e il tipo di token. Entrambi i campi sono obbligatori e ciascuno contiene un solo valore. Cloud IoT Core supporta i seguenti algoritmi di firma:

  • JWT RS256 (RSASSA-PKCS1-v1_5 utilizzando SHA-256 RFC 7518 sec 3.3). Questo valore è espresso nel formato RS256 nel campo alg dell'intestazione JWT.
  • JWT ES256 (ECDSA che utilizza P-256 e SHA-256 RFC 7518 sec 3.4), definito in OpenSSL come curva prime256v1. Questo valore è espresso nel formato ES256 nel campo alg dell'intestazione JWT.

Oltre all'algoritmo di firma, devi fornire il formato del token JWT.

La rappresentazione JSON dell'intestazione è la seguente:

Per le chiavi RSA:

{ "alg": "RS256", "typ": "JWT" }

Per le chiavi a curva ellittica:

{ "alg": "ES256", "typ": "JWT" }

L'algoritmo specificato nell'intestazione deve corrispondere ad almeno una delle chiavi pubbliche registrate per il dispositivo.

Rivendicazioni JWT

Il payload JWT contiene un insieme di rivendicazioni e viene firmato usando le chiavi asimmetriche. Il set di rivendicazioni JWT contiene informazioni sul JWT, ad esempio la sua destinazione, l'emittente, l'ora in cui è stato emesso e/o la durata del token. Come l'intestazione JWT, il set di rivendicazioni JWT è un oggetto JSON e viene utilizzato nel calcolo della firma.

Rivendicazioni obbligatorie

Cloud IoT Core richiede i seguenti campi di richiesta riservati. Le rivendicazioni possono essere mostrate in qualsiasi ordine presente nel set di rivendicazioni.

Nome Descrizione
iat ("Emesso alle ore"): il timestamp della creazione del token, specificato in secondi dalle 00:00:00 UTC del 1° gennaio 1970. Il server potrebbe segnalare un errore se questo timestamp è troppo lontano nel passato o nel futuro (consentendo 10 minuti per disallineare).
exp ("Scadenza"): il timestamp di validità del token, specificato in secondi dalle 00:00:00 UTC del 1° gennaio 1970. La durata massima di un token è 24 ore + disallineamento.
  • Tutte le connessioni MQTT verranno chiuse dal server alcuni secondi dopo la scadenza del token (consentendo così il disallineamento), poiché MQTT non ha un modo per aggiornare le credenziali. Per riconnettersi, devi aver coniato un nuovo token. Tieni presente che, a causa del disallineamento consentito, in pratica la durata minima di un token sarà uguale al disallineamento accettabile dell'orologio, anche se è impostato su un secondo.
  • Quando ti connetti tramite HTTP, ogni richiesta HTTP deve includere un JWT, a prescindere dalla scadenza.
  • Tieni presente che i client nei dispositivi che supportano il protocollo Time Time Protocol (NTP) possono utilizzare Google Public NTP Server per mantenere sincronizzato l'orologio del dispositivo. Il requisito di autenticazione consiste nel mantenere l'orologio sincronizzato con un disallineamento fino a 10 minuti.
aud ("Pubblico"): deve essere una singola stringa contenente l'ID progetto cloud in cui il dispositivo è registrato. Se la richiesta di connessione non corrisponde a questo ID progetto, l'autenticazione verrà rifiutata senza ulteriori analisi.

La rivendicazione nbf("Not Before") viene ignorata e non è obbligatoria.

Di seguito è riportata una rappresentazione JSON dei campi riservati obbligatori in un set di rivendicazioni JWT di Cloud IoT Core:

{
  "aud": "my-project",
  "iat": 1509654401,
  "exp": 1612893233
}

Firma JWT

La specifica JSON Web Signature (JWS) guida la meccanica della generazione della firma per il JWT. L'input per la firma è l'array di byte dei seguenti contenuti:

{Base64url encoded header}.{Base64url encoded claim set}

Per calcolare la firma, firma l'intestazione con codifica base64url, il set di rivendicazioni con codifica base64-url e una chiave segreta (ad esempio un file rsa_private.pem) utilizzando l'algoritmo che hai definito nell'intestazione. La firma viene quindi codificata in base64url e il risultato è il JWT. L'esempio seguente mostra un JWT prima della codifica base64url:

{"alg": "RS256", "typ": "JWT"}.{"aud": "my-project", "iat": 1509654401, "exp": 1612893233}.[signature bytes]

Dopo la codifica finale, il JWT ha il seguente aspetto:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJteS1wcm9qZWN0IiwiZXhwIjoxNTA5NjUwODAxLCJpYXQiOjE1MDk2NTQ0MDF9.F4iKO0R0wvHkpCcQoyrYttdGxE5FLAgDhbTJQLEHIBPsbL2WkLxXB9IGbDESn9rE7oxn89PJFRtcLn7kJwvdQkQcsPxn2RQorvDAnvAi1w3k8gpxYWo2DYJlnsi7mxXDqSUCNm1UCLRCW68ssYJxYLSg7B1xGMgDADGyYPaIx1EdN4dDbh-WeDyLLa7a8iWVBXdbmy1H3fEuiAyxiZpk2ll7DcQ6ryyMrU2XadwEr9PDqbLe5SrlaJsQbFi8RIdlQJSo_DZGOoAlA5bYTDYXb-skm7qvoaH5uMtOUb0rjijYuuxhNZvZDaBerEaxgmmlO0nQgtn12KVKjmKlisG79Q

Aggiornamento dei JWT in corso...

Come descritto nelle rivendicazioni obbligatorie, i token hanno date di scadenza. Se un dispositivo è connesso tramite MQTT e il relativo token scade, si disconnette automaticamente da Cloud IoT Core. Puoi impedire la disconnessione del dispositivo aggiornando automaticamente il relativo token. I seguenti esempi illustrano come verificare se un token è scaduto e, in caso affermativo, come riconnettersi con un nuovo token senza disconnettere il dispositivo.

Java

long secsSinceRefresh = ((new DateTime()).getMillis() - iat.getMillis()) / 1000;
if (secsSinceRefresh > (options.tokenExpMins * MINUTES_PER_HOUR)) {
  System.out.format("\tRefreshing token after: %d seconds%n", secsSinceRefresh);
  iat = new DateTime();
  if ("RS256".equals(options.algorithm)) {
    connectOptions.setPassword(
        createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
  } else if ("ES256".equals(options.algorithm)) {
    connectOptions.setPassword(
        createJwtEs(options.projectId, options.privateKeyFile).toCharArray());
  } else {
    throw new IllegalArgumentException(
        "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
  }
  client.disconnect();
  client.connect(connectOptions);
  attachCallback(client, options.deviceId);
}

Node.js

const secsFromIssue = parseInt(Date.now() / 1000) - iatTime;
if (secsFromIssue > argv.tokenExpMins * 60) {
  iatTime = parseInt(Date.now() / 1000);
  console.log(`\tRefreshing token after ${secsFromIssue} seconds.`);

  client.end();
  connectionArgs.password = createJwt(
    argv.projectId,
    argv.privateKeyFile,
    argv.algorithm
  );
  connectionArgs.protocolId = 'MQTT';
  connectionArgs.protocolVersion = 4;
  connectionArgs.clean = true;
  client = mqtt.connect(connectionArgs);

Python

seconds_since_issue = (datetime.datetime.now(tz=datetime.timezone.utc) - jwt_iat).seconds
if seconds_since_issue > 60 * jwt_exp_mins:
    print("Refreshing token after {}s".format(seconds_since_issue))
    jwt_iat = datetime.datetime.now(tz=datetime.timezone.utc)
    client.loop()
    client.disconnect()
    client = get_client(
        args.project_id,
        args.cloud_region,
        args.registry_id,
        args.device_id,
        args.private_key_file,
        args.algorithm,
        args.ca_certs,
        args.mqtt_bridge_hostname,
        args.mqtt_bridge_port,
    )