Autenticação baseada em token da API REST

122

Estou desenvolvendo uma API REST que requer autenticação. Como a autenticação em si ocorre por meio de um serviço da web externo por HTTP, concluí que distribuiríamos tokens para evitar chamar repetidamente o serviço de autenticação. O que me leva perfeitamente à minha primeira pergunta:

Isso é realmente melhor do que apenas exigir que os clientes usem a autenticação básica HTTP em cada solicitação e fazer chamadas em cache para o serviço de autenticação do lado do servidor?

A solução de autenticação básica tem a vantagem de não exigir uma ida e volta completa ao servidor antes que as solicitações de conteúdo possam começar. Os tokens podem ser potencialmente mais flexíveis no escopo (por exemplo, conceder direitos a recursos ou ações específicos), mas isso parece mais apropriado ao contexto OAuth do que no meu caso de uso mais simples.

Atualmente, os tokens são adquiridos assim:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

O api_key, timestampe verifiersão exigidos por todas as solicitações. O "verificador" é retornado por:

sha1(timestamp + api_key + shared_secret)

Minha intenção é permitir apenas chamadas de partes conhecidas e impedir que as chamadas sejam reutilizadas literalmente.

Isso é bom o suficiente? Underkill? Exagero?

Com um token em mãos, os clientes podem adquirir recursos:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Para a ligação mais simples possível, isso parece horrivelmente detalhado. Considerando que a shared_secretvontade acabará sendo incorporada (no mínimo) a um aplicativo iOS, do qual eu assumiria que pode ser extraído, isso oferece algo além de uma falsa sensação de segurança?

cantlin
fonte
2
Em vez de usar sha1 (timestamp + api_key + shard_secret), você deve usar o hmac (shared_secret, timpestamp + api_key) para obter um melhor hash de segurança en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco
@ MiguelA.Carrasco E em 2017 parece ser o consenso que o bCrypt é a nova ferramenta de hash.
Shawn

Respostas:

94

Deixe-me separar tudo e resolver abordar cada problema isoladamente:

Autenticação

Para autenticação, o baseauth tem a vantagem de ser uma solução madura no nível do protocolo. Isso significa que muitos problemas "podem surgir mais tarde" já estão resolvidos para você. Por exemplo, com BaseAuth, os agentes do usuário sabem que a senha é uma senha e, portanto, não a armazenam em cache.

Carregamento do servidor de autenticação

Se você distribuir um token para o usuário em vez de armazenar em cache a autenticação no servidor, continuará fazendo o mesmo: Armazenando em cache as informações de autenticação. A única diferença é que você está transferindo a responsabilidade pelo armazenamento em cache para o usuário. Isso parece uma mão-de-obra desnecessária para o usuário sem ganhos, por isso recomendo lidar com isso de forma transparente no servidor, conforme sugerido.

Segurança de transmissão

Se você pode usar uma conexão SSL, isso é tudo, a conexão é segura *. Para impedir a execução múltipla acidental, você pode filtrar vários URLs ou solicitar aos usuários que incluam um componente aleatório ("nonce") no URL.

url = username:[email protected]/api/call/nonce

Se isso não for possível e as informações transmitidas não forem secretas, recomendo proteger a solicitação com um hash, conforme sugerido na abordagem de token. Como o hash fornece a segurança, você pode instruir seus usuários a fornecer o hash como a senha básica. Para maior robustez, recomendo o uso de uma sequência aleatória em vez do registro de data e hora como um "nonce" para impedir ataques de repetição (duas solicitações legítimas podem ser feitas durante o mesmo segundo). Em vez de fornecer campos separados "segredo compartilhado" e "chave da API", você pode simplesmente usar a chave da API como segredo compartilhado e, em seguida, usar um sal que não seja alterado para impedir ataques à tabela do arco-íris. O campo de nome de usuário também parece um bom lugar para colocar o nonce, pois faz parte da autenticação. Então agora você tem uma chamada limpa como esta:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:[email protected]/api/call

É verdade que isso é um pouco trabalhoso. Isso ocorre porque você não está usando uma solução em nível de protocolo (como SSL). Portanto, pode ser uma boa ideia fornecer algum tipo de SDK aos usuários, para que pelo menos eles não precisem passar por eles mesmos. Se você precisar fazer dessa maneira, acho o nível de segurança adequado (kill com o botão direito).

Armazenamento secreto seguro

Depende de quem você está tentando impedir. Se você estiver impedindo que pessoas com acesso ao telefone do usuário usem o serviço REST no nome do usuário, seria uma boa ideia encontrar algum tipo de API de chaveiro no sistema operacional de destino e fazer com que o SDK (ou o implementador) armazene o chave lá. Se isso não for possível, você pode pelo menos tornar um pouco mais difícil obter o segredo, criptografando-o e armazenando os dados criptografados e a chave de criptografia em locais separados.

Se você está tentando impedir que outros fornecedores de software obtenham sua chave de API para impedir o desenvolvimento de clientes alternativos, apenas a abordagem de criptografar e armazenar separadamente quase funciona. Essa é uma criptografia de caixa branca e, até o momento, ninguém apresentou uma solução verdadeiramente segura para os problemas dessa classe. O mínimo que você pode fazer é emitir uma única chave para cada usuário, para que você possa banir as chaves abusadas.

(*) EDIT: As conexões SSL não devem mais ser consideradas seguras sem a adoção de etapas adicionais para verificá- las.

cmc
fonte
Obrigado cmc, todos os bons pontos e boa comida para pensar. Acabei adotando uma abordagem de token / HMAC semelhante à que você discutiu acima, como o mecanismo de autenticação da API REST S3 .
Cantlin 26/03/12
Se você armazenar em cache o token no servidor, ele não é essencialmente o mesmo que o bom e antigo ID da sessão? O ID da sessão é de curta duração e também é anexado ao armazenamento em cache rápido (se você o implementar) para evitar atingir seu banco de dados em todas as solicitações. O verdadeiro design RESTful e sem estado não deve ter sessões, mas se você estiver usando um token como um ID e ainda estiver atingindo o banco de dados, não seria melhor usar apenas o ID da sessão? Como alternativa, você pode optar por tokens da web JSON que contêm informações criptografadas ou assinadas para dados inteiros da sessão para um design sem estado real.
#
16

Uma API RESTful pura deve usar os recursos padrão do protocolo subjacente:

  1. Para HTTP, a API RESTful deve estar em conformidade com os cabeçalhos padrão HTTP existentes. A adição de um novo cabeçalho HTTP viola os princípios REST. Não reinvente a roda, use todos os recursos padrão nos padrões HTTP / 1.1 - incluindo códigos de resposta de status, cabeçalhos etc. Os serviços da Web RESTFul devem aproveitar e confiar nos padrões HTTP.

  2. Os serviços RESTful DEVEM SER INTELIGENTES. Quaisquer truques, como autenticação baseada em token, que tenta lembrar o estado das solicitações REST anteriores no servidor, violam os princípios REST. Novamente, isso é uma obrigação; ou seja, se o servidor da web salvar qualquer informação relacionada ao contexto de solicitação / resposta no servidor na tentativa de estabelecer qualquer tipo de sessão no servidor, seu serviço da web NÃO será Stateless. E se NÃO é apátrida, NÃO é RESTFul.

Conclusão: para fins de autenticação / autorização, você deve usar o cabeçalho de autorização padrão HTTP. Ou seja, você deve adicionar o cabeçalho de autorização / autenticação HTTP em cada solicitação subsequente que precisar ser autenticada. A API REST deve seguir os padrões do esquema de autenticação HTTP. As especificações de como esse cabeçalho deve ser formatado são definidas nos padrões HTTP 1.1 da RFC 2616 - seção 14.8 Autorização da RFC 2616 e na autenticação HTTP da RFC 2617: autenticação de acesso básico e digest .

Eu desenvolvi um serviço RESTful para o aplicativo Cisco Prime Performance Manager. Pesquise no Google o documento da API REST que escrevi para esse aplicativo para obter mais detalhes sobre a conformidade da API RESTFul aqui . Nessa implementação, eu escolhi usar o esquema de autorização "Básico" HTTP. - verifique a versão 1.5 ou superior desse documento da API REST e procure autorização no documento.

Rubens Gomes
fonte
8
"Adicionar um novo cabeçalho HTTP viola os princípios REST" Como assim? E se você estiver nisso, pode ser gentil em explicar qual é exatamente a diferença (em relação aos princípios) entre uma senha que expira após um determinado período e um token que expira após um determinado período.
um oliver melhor
6
Nome de usuário + senha é um token (!) Que é trocado entre um cliente e um servidor em cada solicitação. Esse token é mantido no servidor e tem um tempo de vida útil. Se a senha expirar, tenho que adquirir uma nova. Você parece associar "token" a "sessão do servidor", mas essa conclusão é inválida. É ainda irrelevante porque seria um detalhe de implementação. Sua classificação de tokens que não sejam nome de usuário / senha como com estado é puramente artificial.
um oliver melhor
1
Eu acho que você deve motivar por que fazer uma implementação com RESTful sobre autenticação básica, que faz parte da pergunta original. Talvez você também possa vincular alguns bons exemplos com o código incluído. Como iniciante neste assunto, a teoria parece clara o suficiente com muitos recursos bons, mas o método de implementação não é e os exemplos são complicados. Acho frustrante que pareça levar a codificação personalizada para implementar em tempo hábil algo que já foi feito milhares de vezes.
JPK 14/09
13
-1 "Quaisquer truques, como autenticação baseada em token, que tentam lembrar o estado das solicitações REST anteriores no servidor, violam os princípios REST." a autenticação baseada em token não tem nada a ver com o estado das solicitações REST anteriores e não viola o estado sem estado do REST .
Kerem Baydoğan
1
Então, de acordo com isso, os JSON Web Tokens são violação REST porque podem armazenar o estado do usuário (declarações)? De qualquer forma, prefiro violar o REST e usar o bom e antigo ID da sessão como um "token", mas a autenticação inicial é realizada com nome de usuário + senha, assinada ou criptografada usando segredo compartilhado e carimbo de data / hora de vida muito curta (por isso, falha se alguém tentar reproduzir aquele). Em um aplicativo "corporativo", é difícil jogar fora os benefícios da sessão (evitando acessar o banco de dados para alguns dados necessários em quase todas as solicitações); portanto, às vezes precisamos sacrificar a verdadeira apatridia.
#
2

Na web, um protocolo stateful baseia-se em ter um token temporário que é trocado entre um navegador e um servidor (via cabeçalho de cookie ou reescrita de URI) em cada solicitação. Esse token geralmente é criado na extremidade do servidor e é um dado opaco que tem um certo tempo de vida útil e tem o único objetivo de identificar um agente de usuário da web específico. Ou seja, o token é temporário e se torna um ESTADO que o servidor da web deve manter em nome de um agente de usuário do cliente durante a duração dessa conversa. Portanto, a comunicação usando um token dessa maneira é STATEFUL. E se a conversa entre cliente e servidor for STATEFUL, não será RESTful.

O nome de usuário / senha (enviado no cabeçalho da Autorização) geralmente é mantido no banco de dados com a intenção de identificar um usuário. Às vezes, o usuário pode significar outro aplicativo; no entanto, o nome de usuário / senha NUNCA se destina a identificar um agente de usuário cliente Web específico. A conversa entre um agente e servidor da Web com base no uso do nome de usuário / senha no cabeçalho da Autorização (após a Autorização Básica HTTP) é STATELESS porque o front-end do servidor da Web não está criando ou mantendo nenhuma informação STATEqualquer que seja em nome de um agente de usuário do cliente da Web específico. E, com base no meu entendimento do REST, o protocolo declara claramente que a conversa entre clientes e servidor deve ser INTELIGENTE. Portanto, se quisermos ter um serviço RESTful verdadeiro, devemos usar nome de usuário / senha (consulte a RFC mencionada na minha postagem anterior) no cabeçalho da autorização para cada chamada única, NÃO um tipo de token de sensação (por exemplo, tokens de sessão criados em servidores da web) , Tokens OAuth criados em servidores de autorização e assim por diante).

Entendo que vários provedores REST chamados estão usando tokens como OAuth1 ou OAuth2 accept-tokens a serem passados ​​como "Autorização: Portadora" nos cabeçalhos HTTP. No entanto, parece-me que o uso desses tokens para serviços RESTful violaria o verdadeiro significado de STATELESS que o REST adota; porque esses tokens são dados temporários criados / mantidos no lado do servidor para identificar um agente de usuário do cliente da web específico pela duração válida de uma conversa desse cliente da web / servidor. Portanto, qualquer serviço que esteja usando esses tokens OAuth1 / 2 não deve ser chamado de REST se quisermos manter o significado VERDADEIRO de um protocolo STATELESS.

Rubens

Rubens Gomes
fonte