SAP Gateway Zero-to-Hero — Pt.7
CORS — Cross-origin Resource Sharing
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
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:
Que erros foram esses?
No exemplo acima, temos dois erros. Eis o primeiro:
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:
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:
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:
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:
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:
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Request-Headers
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.
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:
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.
Voltando à requisição AJAX
Vamos executar nosso AJAX novamente e ver se agora vai.
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:
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:
Vamos realizar novamente a nossa requisição AJAX. #dedosCruzados
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
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.
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.
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.
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!