Autentikasi antarlayanan

Selain mengautentikasi pengguna, Anda mungkin perlu mengizinkan layanan lain untuk berinteraksi dengan API Anda. Meskipun aplikasi klien dapat memberi pengguna perintah login web untuk mengirimkan kredensial mereka, Anda memerlukan pendekatan lain untuk komunikasi antarlayanan yang aman. Halaman ini menunjukkan pendekatan yang kami rekomendasikan untuk menerapkan autentikasi antar-layanan dan menyediakan kode contoh.

Ringkasan

Untuk mengidentifikasi layanan yang mengirimkan permintaan ke API, gunakan akun layanan. Layanan panggilan menggunakan kunci pribadi akun layanan untuk menandatangani JSON Web Token (JWT) yang aman dan mengirimkan JWT yang ditandatangani dalam permintaan ke API Anda.

Untuk menerapkan autentikasi service-to-service di API dan layanan panggilan Anda:

  1. Buat akun layanan dan kunci yang akan digunakan oleh layanan panggilan.
  2. Tambahkan dukungan untuk autentikasi dalam dokumen OpenAPI untuk layanan Cloud Endpoints Anda.
  3. Tambahkan kode ke layanan panggilan yang:

    • Membuat JWT dan menandatanganinya dengan kunci pribadi akun layanan.
    • Mengirim JWT yang ditandatangani dalam permintaan ke API.

ESP memvalidasi bahwa klaim di JWT cocok dengan konfigurasi dalam dokumen OpenAPI sebelum meneruskan permintaan tersebut ke API Anda. ESP tidak memeriksa izin Cloud Identity yang telah Anda berikan pada akun layanan.

Prasyarat

Halaman ini mengasumsikan bahwa Anda sudah:

Membuat akun layanan dengan kunci

Anda memerlukan akun layanan dengan file kunci pribadi yang digunakan oleh layanan panggilan untuk menandatangani JWT. Jika memiliki lebih dari satu layanan yang mengirimkan permintaan ke API, Anda dapat membuat satu akun layanan untuk mewakili semua layanan panggilan. Jika perlu membedakan layanan—misalnya, layanan tersebut mungkin memiliki izin yang berbeda—Anda dapat membuat akun dan kunci layanan untuk setiap layanan panggilan.

Bagian ini menunjukkan cara menggunakan Konsol Google Cloud dan alat command line gcloud untuk membuat akun layanan dan file kunci pribadi serta menetapkan peran Service Account Token Creator untuk akun layanan. Untuk informasi tentang cara menggunakan API untuk melakukan tugas ini, lihat Membuat dan mengelola akun layanan.

Untuk membuat akun layanan dan kunci:

Konsol Google Cloud

  1. Buat akun layanan:

    1. Di konsol Google Cloud, buka halaman Create service account.

      Buka halaman Create Service Account

    2. Pilih project yang ingin Anda gunakan.

    3. Di kolom Nama akun layanan, masukkan nama.

    4. Opsional: Di kolom Deskripsi akun layanan, masukkan deskripsi.

    5. Klik Create.

    6. Klik kolom Pilih peran. Di bagian All roles, pilih Service Account > Service Account Token Creator.

    7. Klik Done.

      Jangan tutup jendela browser Anda. Anda akan menggunakannya pada langkah berikutnya.

  2. Membuat kunci akun layanan:

    1. Di konsol Google Cloud, klik alamat email untuk akun layanan yang Anda buat.
    2. Klik Kunci.
    3. Klik Tambahkan kunci, lalu Buat kunci baru.
    4. Klik Create. File JSON yang berisi kunci pribadi akun layanan yang didownload ke komputer Anda.
    5. Klik Close.

gcloud

Anda dapat menjalankan perintah berikut menggunakan Google Cloud CLI di komputer lokal atau dalam Cloud Shell.

  1. Setel akun default untuk gcloud. Jika Anda memiliki lebih dari satu akun, pastikan untuk memilih akun yang ada di project Google Cloud yang ingin Anda gunakan.

    gcloud auth login
    
  2. Menampilkan project ID untuk project Google Cloud Anda.

    gcloud projects list
    
  3. Tetapkan project default. Ganti PROJECT_ID dengan project ID Google Cloud yang ingin Anda gunakan.

    gcloud config set project PROJECT_ID
  4. Buat akun layanan. Ganti SA_NAME dan SA_DISPLAY_NAME dengan nama dan nama tampilan yang ingin Anda gunakan.

    gcloud iam service-accounts create SA_NAME \
      --display-name "SA_DISPLAY_NAME"
    
  5. Tampilkan alamat email untuk akun layanan yang baru saja Anda buat.

    gcloud iam service-accounts list
    
  6. Tambahkan peran Service Account Token Creator. Ganti SA_EMAIL_ADDRESS dengan alamat email akun layanan.

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member serviceAccount:SA_EMAIL_ADDRESS \
      --role roles/iam.serviceAccountTokenCreator
    
  7. Buat file kunci akun layanan di direktori kerja saat ini. Ganti FILE_NAME dengan nama yang ingin Anda gunakan untuk file kunci. Secara default, perintah gcloud akan membuat file JSON.

    gcloud iam service-accounts keys create FILE_NAME.json \
      --iam-account SA_EMAIL_ADDRESS
    

Lihat referensi gcloud untuk mengetahui informasi selengkapnya tentang perintah sebelumnya.

Untuk informasi tentang cara mengamankan kunci pribadi, lihat Praktik terbaik untuk mengelola kredensial.

Mengonfigurasi API Anda untuk mendukung autentikasi

Anda harus memiliki objek persyaratan keamanan dan objek definisi keamanan dalam dokumen OpenAPI agar ESP dapat memvalidasi klaim dalam JWT yang ditandatangani.

  1. Tambahkan akun layanan sebagai penerbit dalam dokumen OpenAPI Anda.

    securityDefinitions:
      DEFINITION_NAME:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "SA_EMAIL_ADDRESS"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/SA_EMAIL_ADDRESS"
    
    • Ganti DEFINITION_NAME dengan string yang mengidentifikasi definisi keamanan ini. Anda dapat menggantinya dengan nama akun layanan atau nama yang mengidentifikasi layanan panggilan.
    • Ganti SA_EMAIL_ADDRESS dengan alamat email akun layanan.
    • Anda dapat menentukan beberapa definisi keamanan dalam dokumen OpenAPI, tetapi setiap definisi harus memiliki x-google-issuer yang berbeda. Jika telah membuat akun layanan terpisah untuk setiap layanan panggilan, Anda dapat membuat definisi keamanan untuk setiap akun layanan, misalnya:
    securityDefinitions:
      service-1:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "service-1@example-project-12345.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-1@example-project-12345.iam.gserviceaccount.com"
      service-2:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "service-2@example-project-12345.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/service-2@example-project-12345.iam.gserviceaccount.com"
    
  2. Secara opsional, tambahkan x-google-audiences ke bagian securityDefinitions. Jika Anda tidak menambahkan x-google-audiences, ESP mengharuskan klaim "aud" (audiens) di JWT dalam format https://SERVICE_NAME, dengan SERVICE_NAME adalah nama layanan Endpoint Anda, yang telah dikonfigurasi di kolom host dalam dokumen OpenAPI Anda, kecuali jika tanda --disable_jwt_audience_service_name_check digunakan. Jika flag digunakan dan x-google-audiences tidak ditentukan, kolom JWT aud tidak akan dicentang.

  3. Secara opsional, tambahkan x-google-jwt-locations ke bagian securityDefinitions. Anda bisa menggunakan nilai ini untuk menentukan lokasi JWT kustom. Lokasi JWT default adalah header Authorization (diawali dengan "Bearer "), header X-Goog-Iap-Jwt-Assertion, atau parameter kueri access_token. Catatan:

    • Jika Anda menentukan x-google-jwt-locations, Endpoint akan mengabaikan semua lokasi default.
    • x-google-jwt-locations hanya didukung oleh ESPv2.
  4. Tambahkan bagian security di tingkat atas file (tidak diindentasi atau bertingkat) untuk diterapkan ke seluruh API, atau di tingkat metode untuk diterapkan ke metode tertentu. Jika Anda menggunakan bagian security pada API level dan pada level metode, setelan level metode akan menggantikan setelan level API.

    security:
      - DEFINITION_NAME: []
    
    • Ganti DEFINITION_NAME dengan nama yang Anda gunakan di bagian securityDefinitions.
    • Jika Anda memiliki lebih dari satu definisi di bagian securityDefinitions, tambahkan definisi tersebut di bagian security, misalnya:

      security:
        - service-1: []
        - service-2: []
      
  5. Deploy dokumen OpenAPI yang telah diperbarui. Ganti OPENAPI_DOC dengan nama dokumen OpenAPI Anda.

    gcloud endpoints services deploy OPENAPI_DOC

Sebelum ESP meneruskan permintaan ke API Anda, ESP akan memverifikasi:

  • Tanda tangan JWT menggunakan kunci publik, yang terletak di URI yang ditentukan dalam kolom x-google-jwks_uri dalam dokumen OpenAPI Anda.
  • Bahwa klaim "iss" (penerbit) di JWT cocok dengan nilai yang ditentukan di kolom x-google-issuer.
  • Bahwa klaim "aud" (audiens) di JWT berisi nama layanan Endpoint Anda atau cocok dengan salah satu nilai yang Anda tentukan di kolom x-google-audiences.
  • Bahwa masa berlaku token belum berakhir menggunakan klaim "exp" (waktu habis masa berlaku).

Untuk informasi selengkapnya tentang x-google-issuer, x-google-jwks_uri, x-google-audiences, dan x-google-jwt-locations, lihat ekstensi OpenAPI.

Membuat permintaan terautentikasi ke Endpoints API

Untuk membuat permintaan terautentikasi, layanan panggilan mengirimkan JWT yang ditandatangani oleh akun layanan yang Anda tentukan dalam dokumen OpenAPI. Layanan panggilan harus:

  1. Buat JWT dan tanda tangani dengan kunci pribadi akun layanan.
  2. Kirim JWT yang ditandatangani dalam sebuah permintaan ke API.

Kode contoh berikut menunjukkan proses ini untuk bahasa tertentu. Untuk membuat permintaan terautentikasi dalam bahasa lain, lihat jwt.io untuk mengetahui daftar library yang didukung.

  1. Di layanan panggilan, tambahkan fungsi berikut dan teruskan parameter berikut:
    Java
    • saKeyfile: Jalur lengkap ke file kunci pribadi akun layanan.
    • saEmail: Alamat email akun layanan.
    • audience: Jika Anda menambahkan kolom x-google-audiences ke dokumen OpenAPI, tetapkan audience ke salah satu nilai yang Anda tentukan untuk x-google-audiences. Jika tidak, tetapkan audience ke https://SERVICE_NAME, dengan SERVICE_NAME adalah nama layanan Endpoint Anda.
    • expiryLength: Waktu habis masa berlaku JWT, dalam detik.
    Python
    • sa_keyfile: Jalur lengkap ke file kunci pribadi akun layanan.
    • sa_email: Alamat email akun layanan.
    • audience: Jika Anda menambahkan kolom x-google-audiences ke dokumen OpenAPI, tetapkan audience ke salah satu nilai yang Anda tentukan untuk x-google-audiences. Jika tidak, tetapkan audience ke https://SERVICE_NAME, dengan SERVICE_NAME adalah nama layanan Endpoint Anda.
    • expiry_length: Waktu habis masa berlaku JWT, dalam detik.
    Mulai
    • saKeyfile: Jalur lengkap ke file kunci pribadi akun layanan.
    • saEmail: Alamat email akun layanan.
    • audience: Jika Anda menambahkan kolom x-google-audiences ke dokumen OpenAPI, tetapkan audience ke salah satu nilai yang Anda tentukan untuk x-google-audiences. Jika tidak, tetapkan audience ke https://SERVICE_NAME, dengan SERVICE_NAME adalah nama layanan Endpoint Anda.
    • expiryLength: Waktu habis masa berlaku JWT, dalam detik.

    Fungsi ini membuat JWT, menandatanganinya menggunakan file kunci pribadi, dan menampilkan JWT yang ditandatangani.

    Java
    /**
     * Generates a signed JSON Web Token using a Google API Service Account
     * utilizes com.auth0.jwt.
     */
    public static String generateJwt(final String saKeyfile, final String saEmail,
        final String audience, final int expiryLength)
        throws FileNotFoundException, IOException {
    
      Date now = new Date();
      Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));
    
      // Build the JWT payload
      JWTCreator.Builder token = JWT.create()
          .withIssuedAt(now)
          // Expires after 'expiryLength' seconds
          .withExpiresAt(expTime)
          // Must match 'issuer' in the security configuration in your
          // swagger spec (e.g. service account email)
          .withIssuer(saEmail)
          // Must be either your Endpoints service name, or match the value
          // specified as the 'x-google-audience' in the OpenAPI document
          .withAudience(audience)
          // Subject and email should match the service account's email
          .withSubject(saEmail)
          .withClaim("email", saEmail);
    
      // Sign the JWT with a service account
      FileInputStream stream = new FileInputStream(saKeyfile);
      ServiceAccountCredentials cred = ServiceAccountCredentials.fromStream(stream);
      RSAPrivateKey key = (RSAPrivateKey) cred.getPrivateKey();
      Algorithm algorithm = Algorithm.RSA256(null, key);
      return token.sign(algorithm);
    }
    Python
    def generate_jwt(
        sa_keyfile,
        sa_email="account@project-id.iam.gserviceaccount.com",
        audience="your-service-name",
        expiry_length=3600,
    ):
        """Generates a signed JSON Web Token using a Google API Service Account."""
    
        now = int(time.time())
    
        # build payload
        payload = {
            "iat": now,
            # expires after 'expiry_length' seconds.
            "exp": now + expiry_length,
            # iss must match 'issuer' in the security configuration in your
            # swagger spec (e.g. service account email). It can be any string.
            "iss": sa_email,
            # aud must be either your Endpoints service name, or match the value
            # specified as the 'x-google-audience' in the OpenAPI document.
            "aud": audience,
            # sub and email should match the service account's email address
            "sub": sa_email,
            "email": sa_email,
        }
    
        # sign with keyfile
        signer = google.auth.crypt.RSASigner.from_service_account_file(sa_keyfile)
        jwt = google.auth.jwt.encode(signer, payload)
    
        return jwt
    
    
    Mulai
    
    // generateJWT creates a signed JSON Web Token using a Google API Service Account.
    func generateJWT(saKeyfile, saEmail, audience string, expiryLength int64) (string, error) {
    	now := time.Now().Unix()
    
    	// Build the JWT payload.
    	jwt := &jws.ClaimSet{
    		Iat: now,
    		// expires after 'expiryLength' seconds.
    		Exp: now + expiryLength,
    		// Iss must match 'issuer' in the security configuration in your
    		// swagger spec (e.g. service account email). It can be any string.
    		Iss: saEmail,
    		// Aud must be either your Endpoints service name, or match the value
    		// specified as the 'x-google-audience' in the OpenAPI document.
    		Aud: audience,
    		// Sub and Email should match the service account's email address.
    		Sub:           saEmail,
    		PrivateClaims: map[string]interface{}{"email": saEmail},
    	}
    	jwsHeader := &jws.Header{
    		Algorithm: "RS256",
    		Typ:       "JWT",
    	}
    
    	// Extract the RSA private key from the service account keyfile.
    	sa, err := ioutil.ReadFile(saKeyfile)
    	if err != nil {
    		return "", fmt.Errorf("Could not read service account file: %w", err)
    	}
    	conf, err := google.JWTConfigFromJSON(sa)
    	if err != nil {
    		return "", fmt.Errorf("Could not parse service account JSON: %w", err)
    	}
    	block, _ := pem.Decode(conf.PrivateKey)
    	parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    	if err != nil {
    		return "", fmt.Errorf("private key parse error: %w", err)
    	}
    	rsaKey, ok := parsedKey.(*rsa.PrivateKey)
    	// Sign the JWT with the service account's private key.
    	if !ok {
    		return "", errors.New("private key failed rsa.PrivateKey type assertion")
    	}
    	return jws.Encode(jwsHeader, jwt, rsaKey)
    }
    
  2. Dalam layanan panggilan, tambahkan fungsi berikut untuk mengirim JWT yang ditandatangani dalam header Authorization: Bearer dalam permintaan ke API:
    Java
    /**
     * Makes an authorized request to the endpoint.
     */
    public static String makeJwtRequest(final String signedJwt, final URL url)
        throws IOException, ProtocolException {
    
      HttpURLConnection con = (HttpURLConnection) url.openConnection();
      con.setRequestMethod("GET");
      con.setRequestProperty("Content-Type", "application/json");
      con.setRequestProperty("Authorization", "Bearer " + signedJwt);
    
      InputStreamReader reader = new InputStreamReader(con.getInputStream());
      BufferedReader buffReader = new BufferedReader(reader);
    
      String line;
      StringBuilder result = new StringBuilder();
      while ((line = buffReader.readLine()) != null) {
        result.append(line);
      }
      buffReader.close();
      return result.toString();
    }
    Python
    def make_jwt_request(signed_jwt, url="https://your-endpoint.com"):
        """Makes an authorized request to the endpoint"""
        headers = {
            "Authorization": "Bearer {}".format(signed_jwt.decode("utf-8")),
            "content-type": "application/json",
        }
        response = requests.get(url, headers=headers)
        print(response.status_code, response.content)
        response.raise_for_status()
    
    
    Mulai
    
    // makeJWTRequest sends an authorized request to your deployed endpoint.
    func makeJWTRequest(signedJWT, url string) (string, error) {
    	client := &http.Client{
    		Timeout: 10 * time.Second,
    	}
    
    	req, err := http.NewRequest("GET", url, nil)
    	if err != nil {
    		return "", fmt.Errorf("failed to create HTTP request: %w", err)
    	}
    	req.Header.Add("Authorization", "Bearer "+signedJWT)
    	req.Header.Add("content-type", "application/json")
    
    	response, err := client.Do(req)
    	if err != nil {
    		return "", fmt.Errorf("HTTP request failed: %w", err)
    	}
    	defer response.Body.Close()
    	responseData, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		return "", fmt.Errorf("failed to parse HTTP response: %w", err)
    	}
    	return string(responseData), nil
    }
    

Saat Anda mengirim permintaan menggunakan JWT, untuk alasan keamanan, sebaiknya tempatkan token autentikasi di header Authorization: Bearer. Contoh:

curl --request POST \
  --header "Authorization: Bearer ${TOKEN}" \
  "${ENDPOINTS_HOST}/echo"

dengan ENDPOINTS_HOST dan TOKEN adalah variabel lingkungan yang masing-masing berisi nama host API dan token autentikasi.

Menerima hasil yang diautentikasi di API Anda

ESP biasanya meneruskan semua header yang diterimanya. Namun, metode ini akan menggantikan header Authorization asli saat alamat backend ditentukan oleh x-google-backend dalam spesifikasi OpenAPI atau BackendRule dalam konfigurasi layanan gRPC.

ESP akan mengirimkan hasil autentikasi di X-Endpoint-API-UserInfo ke API backend. Sebaiknya gunakan header ini, bukan header Authorization asli. Header ini adalah string yang base64url mengenkode objek JSON. Format objek JSON berbeda antara ESPv2 dan ESP. Untuk ESPv2, objek JSON persis sama dengan payload JWT asli. Untuk ESP, objek JSON menggunakan nama kolom yang berbeda dan menempatkan payload JWT asli di bagian kolom claims. Lihat Menangani JWT di layanan backend untuk mengetahui informasi format selengkapnya.

Langkah selanjutnya