Autenticação entre serviços

Além de autenticar solicitações de usuários finais, é recomendável autenticar serviços (usuários não humanos) que fazem solicitações à sua API. Nesta página, explicamos como usar contas de serviço para fornecer autenticação para pessoas ou serviços

Visão geral

Para identificar um serviço que envia solicitações para a API, use uma conta de serviço. O serviço de chamada usa a chave privada da conta de serviço para assinar um JSON Web Token (JWT) [link em inglês] seguro e enviá-lo na solicitação para a API.

Para implementar a autenticação da conta de serviço na API e no serviço de chamada:

  1. Crie uma conta de serviço e uma chave que será usada pelo serviço de chamada.
  2. Adicione suporte para autenticação na configuração da API para o serviço de gateway de API.
  3. Adicione o código ao serviço de chamada que:

    • cria um JWT e o assina com a chave privada da conta de serviço;
    • envia o JWT assinado em uma solicitação à API.

O gateway de API valida se as declarações no JWT correspondem à configuração na sua configuração de API antes de encaminhar a solicitação para sua API. O gateway de API não verifica as permissões do Cloud Identity que você concedeu na conta de serviço

Pré-requisitos

Nesta página, presume-se que você já:

Como criar uma conta de serviço com uma chave

Você precisa de uma conta de serviço com um arquivo de chave privada que o serviço de chamada usa para assinar o JWT. Se você tiver mais de um serviço enviando solicitações para sua API, crie uma conta de serviço para representar todos os serviços de chamada. Caso precise diferenciar os serviços crie uma conta de serviço e uma chave para cada serviço de chamada, eles podem ter permissões diferentes, por exemplo.

Esta seção mostra como usar o Console do Google Cloud e a ferramenta de linha de comando gcloud para criar a conta de serviço e o arquivo de chave privada, e para atribuir o papel Criador de token de conta de serviço à conta de serviço. Para informações sobre como executar essa tarefa usando uma API, consulte Como criar e gerenciar contas de serviço.

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

Console do Cloud

Crie uma conta de serviço:

  1. No Console do Cloud, acesse Criar conta de serviço.

    Acesse "Criar conta de serviço"

  2. Selecione um projeto.

  3. No campo Nome da conta de serviço, insira um nome. O Console do Cloud preenche o campo ID da conta de serviço com base nesse nome.

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

  5. Clique em Criar.

  6. Clique no campo Selecionar um papel.

    Em Todos os papéis, selecione Conta de serviço > Criador do token da conta de serviço.

  7. Clique em Continuar.

  8. Clique em Concluído para terminar a criação da conta de serviço.

    Não feche a janela do navegador. Você precisará usá-la no próximo procedimento.

Crie uma chave de conta de serviço:

  1. No Console do Cloud, clique no endereço de e-mail da conta de serviço que você criou.
  2. Clique em Chaves.
  3. Clique em Adicionar chave e, depois, em Criar nova chave.
  4. Clique em Criar. O download de um arquivo de chave JSON é feito no seu computador.
  5. Clique em Fechar.

gcloud

É possível executar os comandos a seguir usando o SDK do Cloud na sua máquina local ou no Cloud Shell.

  1. Defina a conta padrão para gcloud. Se você tiver mais de uma conta, escolha a conta que está no projeto do Google Cloud que quer usar.

    gcloud auth login
    
  2. Exibir os IDs do projeto para seus projetos do Google Cloud.

    gcloud projects list
    
  3. Defina o projeto padrão. Substitua PROJECT_ID pelo ID do projeto do Google Cloud que você quer usar.

    gcloud config set project PROJECT_ID
  4. Crie uma conta de serviço. Substitua SA_NAME e SA_DISPLAY_NAME pelo nome e o nome de exibição que você quer usar.

    gcloud iam service-accounts create SA_NAME \
      --display-name "SA_DISPLAY_NAME"
    
  5. Liste o endereço de e-mail da conta de serviço recém-criada.

    gcloud iam service-accounts list
    
  6. Adicione o papel de criador do token de conta de serviço. Substitua SA_EMAIL_ADDRESS pelo endereço de e-mail 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 arquivo de chave de conta de serviço no diretório de trabalho atual. Substitua FILE_NAME pelo nome escolhido para o arquivo de chaves. Por padrão, o comando gcloud cria um arquivo JSON.

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

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

Para informações sobre como proteger a chave privada, consulte Práticas recomendadas para gerenciar credenciais.

Como configurar a API para dar suporte à autenticação

Ao criar uma configuração da API para seu gateway, você especifica uma conta de serviço que o gateway usa para interagir com outros serviços. Para ativar a autenticação de conta de serviço para serviçosligando seu gateway, modifique o objeto de requisito de segurança eobjeto de definições de segurança na configuração da API. Siga as etapas abaixo para permitir que o gateway de API valide as declarações no JWT assinado usado pelos serviços de chamada.

  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 identifica essa definição de segurança. É possível substituí-lo pelo nome da conta de serviço ou por um nome que identifique o serviço de chamada.
    • Substitua SA_EMAIL_ADDRESS pelo endereço de e-mail da conta de serviço.
    • É possível definir várias definições de segurança na configuração da API, mas cada uma delas precisa ter um x-google-issuer diferente. Caso tenha criado contas de serviço separadas para cada serviço de chamada, crie uma definição de segurança para cada uma delas, 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. Se quiser, adicione x-google-audiences à seção securityDefinitions. Se você não adicionar x-google-audiences, o gateway de API exigirá que a declaração "aud" (público-alvo) no JWT esteja no formato https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço de gateway de API, que você configurou no campo host do documento da OpenAPI.

  3. Adicione uma seção security no nível superior do arquivo (não recuada ou aninhada), a ser aplicada a toda a API, ou no nível dos métodos, a ser aplicada a um método específico. Se você usar seções security no nível da API e do método, as configurações no nível do método modificarão as configurações no nível da API.

    security:
      - DEFINITION_NAME: []
    
    • Substitua DEFINITION_NAME pelo nome usado na seção securityDefinitions.
    • Se você tiver mais de uma definição na seção securityDefinitions, adicione-as na seção security. Por exemplo:

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

Antes de o gateway de API encaminhar uma solicitação para sua API, ele verifica:

  • a assinatura do JWT usando a chave pública, que está localizada no URI especificado no campo x-google-jwks_uri no documento da OpenAPI;
  • se a declaração "iss" (emissor) no JWT corresponde ao valor especificado no campo x-google-issuer;
  • se a declaração "aud" (público) no JWT contém o nome do seu serviço do Endpoints ou corresponde a um dos valores especificados no campo x-google-audiences;
  • se o token não expirou, usando a declaração "exp" (prazo de validade).

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

Como fazer uma solicitação autenticada para uma API Gateway

Para fazer uma solicitação autenticada, o serviço de chamada envia um JWT assinado pela conta de serviço especificada no documento OpenAPI. É preciso que o serviço de chamada:

  1. crie um JWT assinado com a chave privada da conta de serviço;
  2. envie o JWT assinado em uma solicitação à API.

O código de amostra a seguir demonstra esse processo para algumas linguagens. Para fazer uma solicitação autenticada em outras linguagens, consulte jwt.io (em inglês) para ver uma lista de bibliotecas compatíveis.

  1. No serviço de chamada, adicione a seguinte função e transmita os seguintes parâmetros:
    Java
    • saKeyfile: o caminho completo para o arquivo da chave privada da conta de serviço.
    • saEmail: o endereço de e-mail da conta de serviço.
    • audience: se você adicionou o campo x-google-audiences à configuração da API, defina audience como um dos valores especificados para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço de gateway de API.
    • expiryLength: o prazo de validade do JWT, em segundos.
    Python
    • sa_keyfile: o caminho completo para o arquivo da chave privada da conta de serviço.
    • sa_email: o endereço de e-mail da conta de serviço.
    • audience: se você adicionou o campo x-google-audiences à configuração da API, defina audience como um dos valores especificados para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço de gateway de API.
    • expiry_length: o prazo de validade do JWT, em segundos.
    Go
    • saKeyfile: o caminho completo para o arquivo da chave privada da conta de serviço.
    • saEmail: o endereço de e-mail da conta de serviço.
    • audience: se você adicionou o campo x-google-audiences à configuração da API, defina audience como um dos valores especificados para x-google-audiences. Caso contrário, defina audience como https://SERVICE_NAME, em que SERVICE_NAME é o nome do serviço de gateway de API.
    • expiryLength: o prazo de validade do JWT, em segundos.

    A função cria um JWT e o assina usando o arquivo de chave privada. Em seguida, retorna 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
    Go
    
    // 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: %v", err)
    	}
    	conf, err := google.JWTConfigFromJSON(sa)
    	if err != nil {
    		return "", fmt.Errorf("Could not parse service account JSON: %v", err)
    	}
    	block, _ := pem.Decode(conf.PrivateKey)
    	parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    	if err != nil {
    		return "", fmt.Errorf("private key parse error: %v", 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 chamada, adicione a seguinte função para enviar o JWT assinado no cabeçalho Authorization: Bearer na solicitação para a 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()
    
    Go
    
    // 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: %v", 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: %v", err)
    	}
    	defer response.Body.Close()
    	responseData, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		return "", fmt.Errorf("failed to parse HTTP response: %v", err)
    	}
    	return string(responseData), nil
    }
    

Ao enviar uma solicitação usando um JWT, por motivos de segurança, recomenda-se colocar o token de autenticação no cabeçalho Authorization: Bearer. Exemplo:

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

GATEWAY_URL e TOKEN são variáveis de ambiente que contêm o URL do gateway implantado e o token de autenticação, respectivamente.

Como receber resultados autenticados na API

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

O gateway de API enviará o resultado da autenticação no X-Apigateway-Api-Userinfo para a API de back-end. É recomendável usar esse cabeçalho em vez do cabeçalho Authorization original. Esse cabeçalho está codificado em base64url e contém o payload de JWT.

A seguir