Autenticação entre serviços

Além de autenticar os pedidos dos utilizadores finais, pode querer autenticar os serviços (utilizadores não humanos) que fazem pedidos à sua API. Esta página explica como usar contas de serviço para fornecer autenticação a humanos ou serviços.

Vista geral

Para identificar um serviço que envia pedidos para a sua API, usa uma conta de serviço. O serviço de chamadas usa a chave privada da conta de serviço para assinar um símbolo da Web JSON (JWT) seguro e envia o JWT assinado no pedido para a sua API.

Para implementar a autenticação de contas de serviço na sua API e serviço de chamadas:

  1. Crie uma conta de serviço e uma chave para o serviço de chamadas usar.
  2. Adicione suporte para autenticação na configuração da API para o seu serviço API Gateway.
  3. Adicione código ao serviço de chamadas que:

    • Cria um JWT e assina-o com a chave privada da conta de serviço.
    • Envia o JWT assinado num pedido à API.

O API Gateway valida se as reivindicações no JWT correspondem à configuração na configuração da API antes de encaminhar o pedido para a sua API. O API Gateway não verifica as autorizações do Cloud Identity que concedeu na conta de serviço.

Pré-requisitos

Esta página pressupõe que já:

Criar uma conta de serviço com uma chave

Precisa de uma conta de serviço com um ficheiro de chave privada que o serviço de chamadas usa para assinar o JWT. Se tiver mais do que um serviço a enviar pedidos para a sua API, pode criar uma conta de serviço para representar todos os serviços de chamadas. Se precisar de distinguir os serviços, por exemplo, se tiverem autorizações diferentes, pode criar uma conta de serviço e uma chave para cada serviço de chamadas.

Esta secção mostra como usar a Google Cloud consola e a ferramenta de linha de comandosgcloud para criar a conta de serviço e o ficheiro de chave privada, e para atribuir à conta de serviço a função de criador de tokens de conta de serviço. Para obter informações sobre como usar uma API para realizar esta tarefa, consulte o artigo Criar e gerir contas de serviço.

Para criar uma conta de serviço com uma chave:

Google Cloud consola

Crie uma conta de serviço:

  1. Na Google Cloud consola, aceda a Criar conta de serviço.

    Aceda a Criar conta de serviço

  2. Selecione um projeto.

  3. No campo Nome da conta de serviço, introduza um nome. A Google Cloud consola preenche o campo ID da conta de serviço com base neste nome.

  4. Opcional: no campo Descrição da conta de serviço, introduza uma descrição.

  5. Clique em Criar.

  6. Clique no campo Selecionar uma função.

    Em Todas as funções, selecione Contas de serviço > Criador de tokens de contas de serviço.

  7. Clique em Continuar.

  8. Clique em Concluído para terminar de criar a conta de serviço.

    Não feche a janela do navegador. Vai usá-lo no procedimento seguinte.

Crie uma chave de conta de serviço:

  1. Na Google Cloud consola, clique no endereço de email da conta de serviço que criou.
  2. Clique em Chaves.
  3. Clique em Adicionar chave e, de seguida, em Criar nova chave.
  4. Clique em Criar. É transferido um ficheiro de chave JSON para o seu computador.
  5. Clique em Fechar.

gcloud

Pode executar os seguintes comandos através da CLI Google Cloud na sua máquina local ou no Cloud Shell.

  1. Defina a conta predefinida para gcloud. Se tiver mais do que uma conta, certifique-se de que escolhe a conta que está no Google Cloud projeto que quer usar.

    gcloud auth login
    
  2. Apresentar os IDs dos seus Google Cloud projetos.

    gcloud projects list
    
  3. Defina o projeto predefinido. Substitua PROJECT_ID pelo ID do projeto que quer usar. Google Cloud

    gcloud config set project PROJECT_ID
  4. Crie uma conta de serviço. Substitua SA_NAME e SA_DISPLAY_NAME pelo nome e nome a apresentar que quer usar.

    gcloud iam service-accounts create SA_NAME \
      --display-name "SA_DISPLAY_NAME"
  5. Apresentar o endereço de email da conta de serviço que acabou de criar.

    gcloud iam service-accounts list
    
  6. Adicione a função Criador de tokens de contas de serviço. Substitua SA_EMAIL_ADDRESS pelo endereço de email da conta de serviço.

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member serviceAccount:SA_EMAIL_ADDRESS \
      --role roles/iam.serviceAccountTokenCreator
  7. Crie um ficheiro de chave de conta de serviço no diretório de trabalho atual. Substitua FILE_NAME pelo nome que quer usar para o ficheiro de chave. Por predefinição, o comando gcloud cria um ficheiro JSON.

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

Consulte a gcloud referência para mais informações sobre os comandos anteriores.

Para obter informações sobre a salvaguarda da chave privada, consulte as Práticas recomendadas para gerir credenciais.

Configurar a API para suportar a autenticação

Quando cria uma configuração da API para a sua gateway, especifica uma conta de serviço que a gateway usa para interagir com outros serviços. Para ativar a autenticação da conta de serviço para serviços que chamam o seu gateway, modifique o objeto de requisito de segurança e o objeto de definições de segurança na configuração da API. Seguir os passos abaixo permite que o API Gateway valide as reivindicações no JWT assinado usado pelos serviços de chamadas.

  1. Adicione a conta de serviço como emissor na configuração da API.

    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"
    
    • Substitua DEFINITION_NAME por uma string que identifique esta definição de segurança. Pode querer substituí-lo pelo nome da conta de serviço ou por um nome que identifique o serviço de chamadas.
    • Substitua SA_EMAIL_ADDRESS pelo endereço de email da conta de serviço.
    • Pode definir várias definições de segurança na configuração da API, mas cada definição tem de ter um x-google-issuer diferente. Se tiver criado contas de serviço separadas para cada serviço de chamadas, pode criar uma definição de segurança para cada conta de serviço, por exemplo:
    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. Opcionalmente, adicione x-google-audiences à secção securityDefinitions. Se não adicionar x-google-audiences, o API Gateway requer que a reivindicação "aud" (público-alvo) no JWT esteja no formato https://SERVICE_NAME, em que SERVICE_NAME é o nome do seu serviço do API Gateway, que configurou no campo host do seu documento OpenAPI.

  3. Adicione uma secção security ao nível superior do ficheiro (sem indentação nem aninhamento) para aplicar a toda a API ou ao nível do método para aplicar a um método específico. Se usar secções security ao nível da API e ao nível do método, as definições ao nível do método substituem as definições ao nível da API.

    security:
      - DEFINITION_NAME: []
    • Substitua DEFINITION_NAME pelo nome que usou na secção securityDefinitions.
    • Se tiver mais do que uma definição na secção securityDefinitions, adicione-as na secção security, por exemplo:

      security:
        - service-1: []
        - service-2: []
      
  4. Implemente a configuração da API atualizada.

Antes de o API Gateway encaminhar um pedido para a sua API, o API Gateway verifica:

  • A assinatura do JWT através da chave pública, que se encontra no URI especificado no campo x-google-jwks_uri na configuração da API.
  • Que a reivindicação "iss" (emissor) no JWT corresponde ao valor especificado no campo x-google-issuer.
  • Que a reivindicação "aud" (público-alvo) no JWT contém o nome do serviço do API Gateway ou corresponde a um dos valores que especificou no campo x-google-audiences.
  • Que o token não expirou através da reivindicação "exp" (hora de validade).

Para mais informações sobre x-google-issuer, x-google-jwks_uri e x-google-audiences, consulte o artigo Extensões OpenAPI.

Fazer um pedido autenticado a uma API do API Gateway

Para fazer um pedido autenticado, o serviço de chamadas envia um JWT assinado pela conta de serviço que especificou na configuração da API. O serviço de chamadas tem de:

  1. Crie um JWT e assine-o com a chave privada da conta de serviço.
  2. Envie o JWT assinado num pedido à API.

O seguinte exemplo de código demonstra este processo para determinados idiomas. Para fazer um pedido autenticado noutros idiomas, consulte jwt.io para ver uma lista de bibliotecas suportadas.

  1. No serviço de chamadas, adicione a seguinte função e transmita-lhe os seguintes parâmetros:
    Java
    • saKeyfile: o caminho completo para o ficheiro de chave privada da conta de serviço.
    • saEmail: o endereço de email da conta de serviço.
    • audience: se adicionou o campo x-google-audiences à configuração da API, defina audience para um dos valores que especificou para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço API Gateway.
    • expiryLength: o tempo de expiração do JWT, em segundos.
    Python
    • sa_keyfile: o caminho completo para o ficheiro de chave privada da conta de serviço.
    • sa_email: o endereço de email da conta de serviço.
    • audience: se adicionou o campo x-google-audiences à configuração da API, defina audience para um dos valores que especificou para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço API Gateway.
    • expiry_length: o tempo de expiração do JWT, em segundos.
    Ir
    • saKeyfile: o caminho completo para o ficheiro de chave privada da conta de serviço.
    • saEmail: o endereço de email da conta de serviço.
    • audience: se adicionou o campo x-google-audiences à configuração da API, defina audience para um dos valores que especificou para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço API Gateway.
    • expiryLength: o tempo de expiração do JWT, em segundos.

    A função cria um JWT, assina-o através do ficheiro de chave privada e devolve o JWT assinado.

    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
    
    
    Ir
    
    // 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 := os.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. No serviço de chamadas, adicione a seguinte função para enviar o JWT assinado no cabeçalho Authorization: Bearer no pedido à 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()
    
    
    Ir
    
    // 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 := io.ReadAll(response.Body)
    	if err != nil {
    		return "", fmt.Errorf("failed to parse HTTP response: %w", err)
    	}
    	return string(responseData), nil
    }
    

Quando envia um pedido através de um JWT, por motivos de segurança, recomendamos que coloque o token de autenticação no cabeçalho.Authorization: Bearer Por exemplo:

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

onde GATEWAY_URL e TOKEN são variáveis de ambiente que contêm o URL da gateway implementada e o token de autenticação, respetivamente.

Receber resultados autenticados na sua API

Normalmente, o API Gateway encaminha todos os cabeçalhos que recebe. No entanto, substitui o cabeçalho Authorization original quando o endereço do back-end é especificado por x-google-backend na configuração da API.

O gateway da API envia o resultado da autenticação no cabeçalho X-Apigateway-Api-Userinfo para a API de back-end. Recomendamos que use este cabeçalho em vez do cabeçalho Authorization original. Este cabeçalho está codificado em base64url e contém o payload JWT.

O que se segue?