SAP Gateway Zero-to-Hero — Pt.7

CORS — Cross-origin Resource Sharing

Erick Carvalho
Published in
13 min readMar 28, 2018

--

E aweeee meu povo!!!!!

Desde o começo dessa nossa saga de posts venho sempre sugerindo que vocês utilizem o Postman para consumir seus serviços. Isso porque, além da praticidade em poder testar e documentar os seus desenvolvimentos, não queria que vocês quebrassem a cabeça para entender como lidar com:

Same Origin Policy.

Entretanto, quando você começar a criar os seus serviços no dia a dia, eles serão consumidos muitas vezes por aplicações web. Eu juro que da minha experiência com web, CORS foi algo que realmente levou tempo até que eu entendesse o suficiente para me virar bem.

Não vamos, ainda, aprofundar aqui o nosso conhecimento em JavaScript, mas se você deseja consumir os nossos exemplos por uma aplicação web, ou simplesmente testar como isso funcionaria, um dos caminhos é utilizando AJAX.

AJAX

AJAX = Asynchronous JavaScript And XML.

Com o Ajax, os aplicativos Web podem enviar e recuperar dados de um servidor de forma assíncrona sem interferir na exibição e no comportamento da página existente.

Ao desacoplar a camada de troca de dados da camada de apresentação, o Ajax permite que páginas da Web e, por extensão, aplicativos da Web, mudem dinamicamente o seu conteúdo sem a necessidade de recarregar a página inteira.

Veja um pouco mais aqui:

Criando nossa própria requisição AJAX

Não tem ideia do que aconteceu aí em cima? Então corre para estudar…

Neste exemplo, estou usando como base o serviço que desenvolvemos no seguinte post:

Ao executar a página HTML, acima devemos ter algo assim:

Clicando no botão o nosso AJAX será executado, e teremos o seguinte retorno:

Estou utilizando o Chrome com o painel de desenvolvedor ativo. Em Network e Console podemos entender muito bem tudo o que aconteceu.

Que erros foram esses?

No exemplo acima, temos dois erros. Eis o primeiro:

401 — Falta de autorização.

Este erro é, sem dúvida, um dos mais cruéis, isso porque caso você insira este link direto na barra de endereço, terá a seguinte surpresa:

Inserindo o usuário e senha do SAP, você terá a grata surpresa do retorno do seu serviço com um belo sucesso.

Mas caso você insista em voltar na sua página HTML, e executar a requisição AJAX, terá o mesmo erro novamente. É aí que você começa a pirar, pois nada parece fazer sentido.

O segundo erro é o seguinte:

No Access-Control-Allow-Origin

Com este retorno temos a certeza de que a nossa requisição chegou ao servidor. Entretanto, o servidor não a aceitou e nos deu um retorno informando que o Access-Control-Allow-Origin não pode ser null, e por isso o acesso ao servidor foi negado.

Os erros…

Como já adiantei, esses são erros bastante comuns para o pessoal que inicia seus estudos com AJAX, que muitas vezes não entendem o porquê de não ser possível concluir a requisição do serviço para um outro servidor como foi o caso acima.

O erro acima aconteceu simplesmente pelo fato de estarmos executando nossa página HTML localmente (localhost), realizando uma requisição para o servidor do SAP Gateway.

Para entender isso você precisa entender o que é o Same Origin Policy e o Cross-Origin Resource Sharing.

Senta que lá vem história…

Os navegadores possuem uma proteção chamada same origin policy. Essa proteção serve para bloquear requisições para servidores que não são da mesma origem que o seu, por exemplo:

Do nosso server (localhost) estamos realizando uma requisição para outro server, no caso o nosso SAP Gateway. Isso é um exemplo claro de origens distintas.

Isso implica que você, ao tentar realizar um AJAX (enviar uma requisição) do seu localhost para outro servidor, infringe a política do navegador por não estar na mesma origem, ou seja, os dois no mesmo endereço (o que significaria ambos no endereço localhost ou 173.194.219.94).

E quando digo que deve ser utilizada a mesma origem, é realmente a mesma origem (incluindo a porta). Até se o protocolo utilizado mudar, por exemplo, uma requisição em que um utilize HTTP e o outro seja HTTPS, você deverá ter o mesmo erro.

Então, para sermos capazes de lidar com essa que é, acima de tudo, uma proteção contra scripts maliciosos, precisamos garantir que toda requisição AJAX possua:

Protocolo + host + porta iguais

Por que tudo isso?

Antes do same origin policy, o que acontecia é que o usuário, ao tentar acessar um site qualquer, por qualquer vacilo, digitava (por exemplo) o endereço errado — ao invés de digitar o endereço www.meusite.com.br, acabava por digitar www.meusaiti.com.br.

Alguém percebeu que esse erro é mais comum do que parece, e então decidiu criar uma página com o endereço www.meusaiti.com.br — e este é o que chamamos de servidor malicioso, ou em termos práticos, este será um servidor para roubar informações.

Daí, quando alguém erra novamente o endereço, ao invés de ser direcionado para uma página de erro, com a informação que aquele site não existe, o que passa a acontecer é que ele acaba por acessar um servidor malicioso que irá apresentar a ele uma página idêntica a que era esperada.

É assim que o usuário passa a navegar em páginas falsas, inserindo informações confidenciais, enquanto que o servidor malicioso passa a realizar requisições AJAX para o servidor verdadeiro. Dependendo das informações que ele já conseguiu roubar, ele apenas redireciona o usuário para o servidor verdadeiro.

Isto ocorria pois não havia controle sobre requisições de origens diferentes. Mas e ai, como contornamos isso? Eis que surge o CORS:

Ilustração do exemplo acima

CORS — Cross-Origin Resource Sharing

Trocando em miúdos, o CORS é uma configuração realizada no servidor (preste bastante atenção nisso: não adianta tentar lutar no lado do cliente - navegador - tudo será em vão) para permitir requisições de determinadas origens.

Logo, é possível realizar configurações no nosso servidor para permitir que nós apenas aceitemos requisições de certas origens.

Essa configuração se dará indicando o atributo:

Access-Control-Allow-Origin

[OFF-Topic]

Existem diversos outros atributos que podem ser utilizados. São eles:

  1. Access-Control-Allow-Credentials
  2. Access-Control-Allow-Headers
  3. Access-Control-Allow-Methods
  4. Access-Control-Allow-Origin
  5. Access-Control-Expose-Headers
  6. Access-Control-Max-Age
  7. Access-Control-Request-Headers
  8. Access-Control-Request-Method

Para mais informações sobre cabeçalho:

[/OFF-Topic]

Este é um dos atributos possíveis que é informado no cabeçalho HTTP. Ele é um tipo de atributo adicional que visa garantir permissão de acesso a recursos de um servidor diferente do servidor de origem da página (domínio).

Então, sempre que tivermos scripts que solicitem algo de um endereço de servidor diferente do nosso domínio, automaticamente os navegadores irão restringir requisições HTTP.

Exemplo do que acontece quando executamos uma requisição. Preflight Request não é para todas as requisições. Vamos ver um pouco mais a respeito logo mais, aqui neste post. Aguarde e confie.

Antes de entendermos como configurar o servidor para aceitar requisições de outras origens, vamos entender um pouco mais sobre os dois tipos de CORS que existem.

O primeiro é o tipo Requisição simples, que possui as seguintes características.

Utilizar um dos verbos abaixo:

  • HEAD
  • GET
  • POST

Além dos verbos acima, em seu cabeçalho, APENAS poderemos utilizar os atributos abaixo:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type

Qualquer coisa além disso irá descaracterizar a requisição como simples

Uma requisição que respeita os termos acima é caracterizada como simples pois dispensa a validação conhecida como Preflight Request.

Preflight Request

Preflight é um um tipo de requisição que verifica se o protocolo CORS é entendido e aceito pelo lado do servidor. Um preflight request é algo que é disparado automaticamente por um navegador quando a requisição não é simples. Isso quer dizer que quem está cuidando do frontend dificilmente irá se preocupar em controlar este tipo de requisição.

Requisições que não são simples

A partir do momento em que utilizamos verbos HTTP que não estão na lista acima, como os verbos PUT, DELETE ou qualquer outro, ou caso você deseje incrementar o cabeçalho da requisição com opções além das apresentadas acima, como por exemplo:

headers{ 
"Auth": "Basic QmFyOCBvIGJsb2cgZG8gZGVzZW52b2x2ZWRvciBtb2Rlcm5v" }

Você precisa lidar com uma requisição que não é simples. — Sei que é bizarro estar chamando de requisição simples e não simples, mas é que nunca encontrei um nome técnico para este tipo de situação.

Logo, uma requisição que não é simples faz o navegador disparar uma requisição de preflight para o servidor. De forma prática, isso significa basicamente que o navegador está garantindo que não irá mandar uma requisição "fora do padrão" para o servidor. Quando da chegada da requisição no servidor, descobre-se que aquela requisição está muito fora do padrão, e por isso este não irá aceitá-la.

Esse preflight se dará por meio do verbo OPTIONS. Sempre que uma requisição com este verbo chegar ao seu servidor, tenha certeza de que é um preflight acontecendo.

Assim, o navegador com o preflight enviado e respondido com sucesso inicia a requisição real com a certeza de que as permissões de acesso ao servidor serão concedidas.

Por conta de performance a resposta de preflight também pode ser armazenada em cache para que ela não seja emitida em todas as solicitações. Cuide para que quem desenvolver o frontend esteja atento a isto.

Finalmente: Como configurar o CORS no SAP Gateway

Voltando para nossa aplicação, agora sabemos que é possível realizar requisição no nosso servidor SAP Gateway. Entretanto, será necessário configurar o servidor antes.

A configuração é bem simples e apenas precisaremos configurar o cabeçalho, ficando algo como:

Access-Control-Allow-Origin: "http://URL:PORTA" ou * (asterisco) 

Assim como fizemos no post SAP Gateway Zero-to-Hero — Pt.4 Internet Communication Framework (ICF), vamos criar uma classe customizada para que a ICF comporte-se exatamente como desejamos.

Primeiramente criaremos a classe Z_CL_BAR8_SERVICE_TEST com a seguinte configuração:

Herde a classe default /IWFND/CL_SODATA_HTTP_HANDLER
Com a herança feita, deverá aparecer que você está implementando a interface IF_HTTP_EXTENSION
Então, vá para os métodos e note que já existirá uma implementação para o método HANDLE_REQUEST. Como queremos realizar a nossa própria customização, clique na opção para REDEFINIR MÉTODO e vamos à implementação.
Perceba que realizei configurações via código ara permitir acesso de qualquer Origem (linha 59). Além disso, criamos uma configuração para acessar diversos verbos HTTP (linha 32). Ative a classe e vamos que vamos! Observações: Esta classe é meramente ilustrativa, por favor cuide para deixar o código mais limpo e funcional, minha preocupação foi apenas que você pudesse explorar os recursos que são possíveis de se acessar de dentro de um HandlerList.

Essa forma de permitir chamar uma outra origem também é chamado de Cross-Origin Resource Sharing ou CORS.

Que esteja claro que isso não é uma configuração do jQuery ou AJAX em geral. Isso é algo que o servidor precisa se preocupar, e então adicionar na resposta HTTP.

Com nossa classe pronta, vamos ajustar a configuração no ICF para que ela seja chamada.

Entre na transação SICF > vá ao nó do seu serviço > HanderList (Lista de manipulação). Substitua o que antes estava como /IWFND/CL_SODATA_HTTP_HANDLER para a classe que acabamos de criar — no meu caso Z_CL_BAR8_SERVICE_TEST. SALVE essa nova configuração e, por segurança, aconselho que você desative este nó e ative-o novamente, apenas para limpar qualquer cache.

Voltando à requisição AJAX

Vamos executar nosso AJAX novamente e ver se agora vai.

Erro novamente =/

Mesmo realizando toda aquela configuração do lado do servidor, alguma coisa ainda está errada — mas perceba que o navegador não está reclamando APENAS do Access-Control-Allow-Origin, ele também está reclamando pois estamos enviando o Origin igual a NULL.

Claro que se você está sozinho desbravando todos esses detalhes, a única coisa que você é capaz de ver a essa altura, é que está tudo vermelho e você ainda continua com o erro 401 — Não Autorizado.

Olhando com mais atenção a ferramenta de análise do Chrome, perceberemos o seguinte:

Sempre procure analisar os cabeçalhos de cada requisição. Eles guardam muitos segredos.

O Origin, no meu caso, está sendo executado desta forma, simplesmente pelo fato deu estar executando esse AJAX diretamente de um arquivo local na minha máquina. Temos algumas formas de contornar esta situação, e hoje vou lhes apresentar a três formas que já utilizei:

Extensão do Chrome que fixa um Origin para que você seja capaz de executar uma requisição AJAX de um arquivo local

ou

Executar a nossa requisição AJAX através de um servidor web. Para esta opção poderíamos utilizar o Apache, IIS, PHP, NodeJS e vários outros. Entretanto, hoje vou mostrar como fazer isso utilizando NodeJS.

ou

Alterando diretamente o SAP NetWeaver Application Server — Os Basis píra

O seguinte trecho deste post mostrará as três opções, entretanto elas me levaram ao mesmo lugar. Fica a seu critério realizar as três, ou apenas uma delas. Caso opte por realizar apenas uma, ao finalizar basta ir direto para — Autenticação no SAP.

Extensão do Chrome

Sem dúvidas essa é a opção mais fácil, porém a menos divertida. Basta acessar e instalar:

Com a extensão instalada, o navegador passará a ter o seguinte botão:

Clique em Enable cross-origin resource-sharing e então este botão ficará verde.

Vamos realizar novamente a nossa requisição AJAX. #dedosCruzados

Erro novamente e não foi dessa vez. Mas….

Tivemos erro novamente, mas perceba que agora o servidor aceitou a nossa requisição, e já não estamos mais com erro de envio do Origin igual a Null. Isso porque essa extensão do Chrome, quando está ativa, inclui no Origin o endereço http://evil.com. Como deixamos aberto a qualquer origem acessar nosso servidor, simplesmente ele aceitou, validando a nossa requisição sem problemas.

Esta opção é bastante útil, entretanto apenas servirá para testes rápidos, mais cedo ou mais tarde você precisará de um servidor web.

Outro modo é realizando AJAX através de um servidor web

Servidor e configuração da rota
EJS View

Configurando o SAP Netweaver Application Server

Para esta opção, irei indicar um excelente post no blog da SAP, que eu não teria condições de fazer melhor. Se eu tivesse descoberto esta postagem antes, teria poupado muito tempo:

Autenticação no SAP — O erro que ocorre nos três modos citados acima

E seja qual for o caminho que você seguiu para chegar até aqui, ainda estamos com o seguinte erro:

Como fizemos no começo deste post, vamos acessar mais uma vez este link diretamente pela barra de endereços.

Caso você insira corretamente o seu usuário e senha você deverá ter o retorno do seu serviço.

Perceba que sempre que acessamos o serviço diretamente pela barra de endereço, este popup de autenticação é exibido. E aí está o problema da nossa requisição, em nenhum momento estamos cuidando da autenticação. Para isto, vamos incrementar o seguinte trecho de código no nosso AJAX.

Implemente no seu AJAX o atributo: xhrFields com a opção withCredentials igual a true

Mais uma vez vamos lá executar o nosso AJAX. Você perceberá que aquele popup de autenticação agora será exibido, então você deve preenchê-lo, obtendo o seguinte resultado:

Por mais que seja desesperador quando você está aprendendo a lidar com essas requisições AJAX, ainda assim você percebe que tudo possui uma certa lógica.

O erro que acabou de acontecer é porque o navegador e o servidor não aceitam que você utilize o atributo xhrFields com a opção withCredentials igual a true, mas com a origem da requisição genérica, ou seja, passando um wildcard (*) como opção para o atributo Access-Control-Allow-Origin.

Para lidar com este problema temos duas opções:

Alterarmos a configuração de origem no servidor, informando exatamente qual será o endereço da origem:

Entretanto, utilizando AJAX da forma como estamos utilizando, sempre se fará necessário a abertura do popup de autenticação. Se você estiver utilizando o SAPUI5 ou OpenUI5 você terá uma maneira mais elegante de lidar com isso, e então não teremos esse popup que pode ficar ruim dependendo do layout da sua aplicação ou até mesmo em questão de usabilidade.

Outro modo…

A outra opção que existe é a de inserirmos um usuário de comunicação (no seu caso você poderá com o seu usuário de diálogo, sem problemas), para efetuar logon automático. Assim, você poderá realizar as tratativas necessárias referentes à autenticação já de dentro do SAP Gateway.

Inserir um usuário de logon nesta tela faz com que não seja mais necessário implementar xhrFields com a opção withCredentials igual a true em seu AJAX. O AJAX será capaz de requisitar o servidor caso o cabeçalho CORS esteja de acordo.

Meus dois centavos sobre segurança

Enquanto o CORS estabelece as bases para a realização de solicitações entre domínios, de forma alguma venha pensar que os cabeçalhos CORS são um substituto para práticas de segurança.

CORS ajuda com scripts maliciosos, o que não garante a segurança do seu site. Novamente: o CORS fornece instruções ao navegador sobre o acesso entre domínios. Então trate de ler mais sobre:

Lembre-se que estamos agora em uma selva chamada internet. Seja profissional.

Então é isso galera muito obrigado pelo minutinhos de leitura. Não deixe de conferir as referências abaixo, e se curtiu o post não deixe de participar comentando, compartilhando ou simplesmente segurando esse botão de aplausos para mostrar que é esse tipo de conteúdo que você deseja ter por aqui. Muito obrigado e até mais!

#Bar8Indica

#LeiaTambém

--

--

Editor for

Agilista, Desenvolvedor e quando não está discutindo TDD está sendo repreendido por algum comentário infeliz