Adicionar mais cabeçalhos de solicitação HTTP

As solicitações HTTP contêm cabeçalhos como User-Agent ou Content-Type. Além dos cabeçalhos anexados pelos navegadores, os apps Android podem adicionar outros cabeçalhos, como Cookie ou Referrer, usando o extra de intent EXTRA_HEADERS. Por motivos de segurança, o Chrome filtra alguns dos cabeçalhos extras dependendo de como e onde uma intent é iniciada.

As solicitações entre origens exigem uma camada extra de segurança, já que o cliente e o servidor não pertencem à mesma parte. Este guia discute o lançamento dessas solicitações pelas guias personalizadas do Chrome, ou seja, intents iniciados por apps que abrem um URL na guia do navegador. Até o Chrome 83, os desenvolvedores podiam adicionar qualquer cabeçalho ao iniciar uma guia personalizada. A partir da versão 83, o Chrome começou a filtrar todos os cabeçalhos entre origens, exceto os permitidos, já que os cabeçalhos não permitidos representavam um risco de segurança. A partir do Chrome 86, é possível anexar cabeçalhos não permitidos a solicitações de origem cruzada quando o servidor e o cliente estão relacionados usando um link de recurso digital. Esse comportamento está resumido na tabela a seguir:

Versão do Chrome Cabeçalhos CORS permitidos
antes do Chrome 83 approvelisted, non-approvelisted
Chrome 83 a 85 na lista de permissão
no Chrome 86 e em versões mais recentes na lista de permissões e não na lista de permissões quando um link de ativo digital é configurado

Tabela 1: Filtragem de cabeçalhos CORS não incluídos na lista de permissões.

Este artigo mostra como configurar uma conexão verificada entre o servidor e o cliente e usar isso para enviar cabeçalhos HTTP na lista de permissões e fora dela. Você pode pular para Como adicionar cabeçalhos extras a intents de guias personalizadas para conferir o código.

Contexto

Cabeçalhos de solicitação CORS na lista de aprovação x cabeçalhos de solicitação CORS fora da lista de aprovação

O Compartilhamento de recursos entre origens (CORS) permite que um aplicativo da Web de uma origem solicite recursos de uma origem diferente. A lista de cabeçalhos CORS-approvelisted é mantida no HTML Standard. Exemplos de cabeçalhos na lista de permissões são mostrados na próxima tabela:

Cabeçalho Descrição
accept-language anuncia idiomas naturais que o cliente entende
content-language descreve a linguagem destinada ao público atual
content-type indica o tipo de mídia do recurso

Tabela 2: Exemplo de cabeçalhos CORS na lista de permissões.

Os cabeçalhos na lista de permissões são considerados seguros porque não contêm informações sensíveis do usuário e não causam operações potencialmente prejudiciais no servidor.

Confira exemplos de cabeçalhos não aprovados na tabela a seguir:

Cabeçalho Descrição
bearer-token autentica o cliente em um servidor
origem indica a origem da solicitação
biscoito contém cookies definidos pelo servidor

Tabela 3: Exemplo de cabeçalhos CORS não aprovados.

O padrão HTML desencoraja a anexação de cabeçalhos não aprovados a solicitações de CORS, e os servidores presumem que as solicitações entre origens contêm apenas cabeçalhos aprovados. O envio de cabeçalhos não aprovados de domínios de origem cruzada permitiria que apps maliciosos de terceiros criassem cabeçalhos que usam cookies de usuário armazenados e anexados a solicitações pelo Chrome (ou outro navegador). Os cookies podem autenticar transações maliciosas do servidor que não seriam possíveis de outra forma.

Como anexar cabeçalhos aprovados pelo CORS a solicitações de guias personalizadas

As guias personalizadas são uma maneira especial de abrir páginas da Web em uma guia do navegador personalizada. É possível criar intents de guia personalizada usando CustomTabsIntent.Builder(). Também é possível anexar cabeçalhos a essas intents usando um Bundle com a flag Browser.EXTRA_HEADERS:

CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

Bundle headers = new Bundle();
headers.putString("bearer-token", "Some token");
headers.putString("redirect-url", "Some redirect url");   
intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);

intent.launchUrl(Activity.this, Uri.parse("http://www.google.com"));

Sempre podemos anexar cabeçalhos na lista de permissões a solicitações de CORS de guias personalizadas. No entanto, o Chrome filtra os cabeçalhos não aprovados por padrão. Embora outros navegadores possam ter comportamentos diferentes, os desenvolvedores devem esperar que os cabeçalhos não aprovados sejam bloqueados em geral.

A maneira aceita de incluir cabeçalhos não aprovados em guias personalizadas é primeiro verificar a conexão entre origens usando um link de acesso digital. A próxima seção mostra como configurar esses cabeçalhos e iniciar uma intent do Custom Tabs com eles.

Como adicionar cabeçalhos extras a intents de guias personalizadas

Para permitir que cabeçalhos não aprovados sejam transmitidos por intents de guias personalizadas, é necessário configurar um link de recurso digital entre o aplicativo Android e o aplicativo da Web que verifique se o autor é proprietário dos dois aplicativos.

Siga o guia oficial para configurar uma vinculação de recursos digitais. Para a relação de link, use "delegate_permission/common.use_as_origin", que indica que os dois apps pertencem à mesma origem depois que o link é verificado.

Criar uma intent de guia personalizada com cabeçalhos extras

Há várias maneiras de criar uma intent do Custom Tabs. Você pode usar o builder disponível no AndroidX adicionando a biblioteca às dependências de build:

implementation 'androidx.browser:browser:1.2.0'

Crie a intent e adicione cabeçalhos extras:

CustomTabsIntent constructExtraHeadersIntent(CustomTabsSession session) {
    CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

    // Example non-cors-approvelisted headers.
    Bundle headers = new Bundle();
    headers.putString("bearer-token", "Some token");
    headers.putString("redirect-url", "Some redirect url");
    intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
    return intent;
}

Uma conexão de guias personalizadas é usada para configurar um CustomTabsSession entre o app e a guia do Chrome. Precisamos da sessão para verificar se o app e o web app pertencem à mesma origem. A verificação só será aprovada se os links de recursos digitais estiverem configurados corretamente.

Recomendamos ligar para CustomTabsClient.warmup(). Ele permite que o aplicativo do navegador faça a pré-inicialização em segundo plano e acelere o processo de abertura de URL.

// Set up a connection that warms up and validates a session.
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(@NonNull ComponentName name, 
        @NonNull CustomTabsClient client) {
        // Create session after service connected.
        mSession = client.newSession(callback);
        client.warmup(0);
        // Validate the session as the same origin to allow cross origin headers.
        mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, 
            Uri.parse(url), null);
    }
    @Override
    public void onServiceDisconnected(ComponentName componentName) { }
};

Configurar um callback que inicia a intent após a validação

O CustomTabsCallback foi transmitido para a sessão. Configuramos o onRelationshipValidationResult() para iniciar o CustomTabsIntent criado anteriormente depois que a verificação de origem for concluída.

// Set up a callback that launches the intent after session validated.
CustomTabsCallback callback = new CustomTabsCallback() {
    @Override
    public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, 
        boolean result, @Nullable Bundle extras) {
        // Launch custom tabs intent after session was validated as the same origin.
        CustomTabsIntent intent = constructExtraHeadersIntent(mSession);
        intent.launchUrl(MainActivity.this, Uri.parse(url));
    }
};

Vincular a conexão de serviço das guias personalizadas

A vinculação do serviço inicia o serviço, e o onCustomTabsServiceConnected() da conexão será chamado eventualmente. Não se esqueça de desvincular o serviço adequadamente. A vinculação e a desvinculação são feitas geralmente nos métodos de ciclo de vida da atividade onStart() e onStop().

// Bind the custom tabs service connection.
// Call this in onStart()
CustomTabsClient.bindCustomTabsService(this,
    CustomTabsClient.getPackageName(MainActivity.this, null), connection);

// …
// Unbind the custom tabs service.
// Call this in onStop().
unbindService(connection);

Código do aplicativo de demonstração

Saiba mais sobre o serviço de guias personalizadas aqui. Consulte o repositório android-browser-helper no GitHub para conferir um exemplo de app funcional.

Resumo

Este guia mostrou como adicionar cabeçalhos arbitrários a solicitações CORS de guias personalizadas. Os cabeçalhos aprovados podem ser anexados a todas as solicitações CORS de guias personalizadas. Os cabeçalhos não incluídos na lista de aprovação geralmente são considerados não seguros em solicitações de CORS, e o Chrome os filtra por padrão. A anexação é permitida apenas para clientes e servidores da mesma origem, verificados por uma vinculação de recursos digitais.