Estratégia para gerar identificadores únicos e seguros para uso em um aplicativo Web "às vezes offline"


47

Eu tenho um projeto baseado na Web que permite que os usuários trabalhem online e offline e estou procurando uma maneira de gerar IDs exclusivos para registros no lado do cliente. Eu gostaria de uma abordagem que funcione enquanto um usuário estiver offline (ou seja, incapaz de conversar com um servidor), seja garantido que seja único e seguro. Por "seguro", estou especificamente preocupado com os clientes que enviam IDs duplicados (maliciosamente ou não) e, portanto, causam estragos na integridade dos dados.

Venho pesquisando no Google, esperando que este já seja um problema resolvido. Não encontrei nada muito definitivo, especialmente em termos de abordagens usadas em sistemas de produção. Encontrei alguns exemplos de sistemas em que os usuários acessam apenas os dados que eles criaram (por exemplo, uma lista Todo que é acessada em vários dispositivos, mas apenas pelo usuário que os criou). Infelizmente, preciso de algo um pouco mais sofisticado. Encontrei algumas idéias realmente boas aqui , que estão alinhadas com a maneira como eu achava que as coisas poderiam funcionar.

Abaixo está a minha solução proposta.

Alguns requisitos

  1. Os IDs devem ser globalmente exclusivos (ou pelo menos exclusivos dentro do sistema)
  2. Gerado no cliente (ou seja, via javascript no navegador)
  3. Seguro (conforme descrito acima e de outra forma)
  4. Os dados podem ser visualizados / editados por vários usuários, incluindo usuários que não os criaram.
  5. Não causa problemas significativos de desempenho para db de back-end (como MongoDB ou CouchDB)

Solução proposta

Quando os usuários criam uma conta, eles recebem um uuid que foi gerado pelo servidor e conhecido por ser exclusivo no sistema. Esse ID NÃO deve ser o mesmo que o token de autenticação do usuário. Vamos chamar esse id de "token de id" do usuário.

Quando um usuário cria um novo registro, ele gera um novo uuid em javascript (gerado usando window.crypto quando disponível. Veja exemplos aqui ). Esse ID é concatenado com o "token de ID" que o usuário recebeu quando criou sua conta. Esse novo ID composto (token de ID do servidor + uuid do cliente) agora é o identificador exclusivo do registro. Quando o usuário está online e envia esse novo registro ao servidor back-end, o servidor:

  1. Identifique isso como uma ação "inserir" (ou seja, não uma atualização ou exclusão)
  2. Validar que ambas as partes da chave composta são uuids válidos
  3. Valide se a parte do "ID token" fornecida do ID composto está correta para o usuário atual (ou seja, corresponde ao token de ID do servidor atribuído ao usuário quando ele criou sua conta)
  4. Se tudo estiver copasetic, inserir os dados no db (tendo o cuidado de fazer uma inserção e não um "upsert", de modo que se a id faz já existe não atualizar um registro existente por engano)

Consultas, atualizações e exclusões não exigiriam nenhuma lógica especial. Eles simplesmente usariam o ID para o registro da mesma maneira que os aplicativos tradicionais.

Quais são as vantagens dessa abordagem?

  1. O código do cliente pode criar novos dados enquanto estiver offline e conhecer o ID desse registro imediatamente. Eu considerei abordagens alternativas nas quais um ID temporário seria gerado no cliente, que posteriormente seria trocado por um ID "final" quando o sistema estivesse online. No entanto, isso pareceu muito frágil. Especialmente quando você começa a pensar em criar dados filho com chaves estrangeiras que também precisariam ser atualizadas. Sem mencionar como lidar com URLs que mudam quando o ID é alterado.

  2. Ao transformar os IDs em um composto de um valor gerado pelo cliente E em um valor gerado pelo servidor, cada usuário cria efetivamente os IDs em uma caixa de proteção. Isso tem como objetivo limitar os danos que podem ser causados ​​por um cliente mal-intencionado / desonesto. Além disso, qualquer colisão de ID é por usuário, não global para todo o sistema.

  3. Como um token de identificação de usuário está vinculado à sua conta, as identificações só podem ser geradas em uma caixa de proteção de usuários por clientes autenticados (ou seja, onde o usuário efetuou login com êxito). Isso tem como objetivo impedir que clientes mal-intencionados criem identificações incorretas para um usuário. Obviamente, se um token de autenticação de usuário for roubado por um cliente mal-intencionado, ele poderá fazer coisas ruins. Porém, depois que um token de autenticação é roubado, a conta fica comprometida. Caso isso acontecesse, o dano causado seria limitado à conta comprometida (não todo o sistema).

Preocupações

Aqui estão algumas das minhas preocupações com essa abordagem

  1. Isso gerará IDs suficientemente exclusivos para um aplicativo em larga escala? Existe alguma razão para pensar que isso resultará em colisões de identificação? O javascript pode gerar um uuid suficientemente aleatório para que isso funcione? Parece que o window.crypto está amplamente disponível e esse projeto já requer navegadores razoavelmente modernos. ( esta questão agora tem uma questão SO própria separada )

  2. Faltam algumas brechas que podem permitir que um usuário mal-intencionado comprometa o sistema?

  3. Há motivos para se preocupar com o desempenho do banco de dados ao consultar uma chave composta composta por 2 uuids. Como esse ID deve ser armazenado para obter o melhor desempenho? Dois campos separados ou um único campo de objeto? Haveria uma abordagem "melhor" diferente para Mongo vs Couch? Eu sei que ter uma chave primária não sequencial pode causar problemas de desempenho notáveis ​​ao fazer inserções. Seria mais inteligente ter um valor gerado automaticamente para a chave primária e armazenar esse ID como um campo separado? ( esta questão agora tem uma questão SO própria separada )

  4. Com essa estratégia, seria fácil determinar que um conjunto específico de registros foi criado pelo mesmo usuário (já que todos compartilhariam o mesmo token de identificação publicamente visível). Embora eu não tenha problemas imediatos com isso, é sempre melhor não vazar mais informações sobre detalhes internos do que o necessário. Outra possibilidade seria o hash da chave composta, mas isso pode parecer mais complicado do que vale a pena.

  5. Caso haja uma colisão de ID para um usuário, não há uma maneira simples de recuperar. Suponho que o cliente possa gerar um novo ID, mas isso parece muito trabalho para um caso de ponta que realmente nunca deveria acontecer. Eu pretendo deixar isso sem endereço.

  6. Somente usuários autenticados podem visualizar e / ou editar dados. Essa é uma limitação aceitável para o meu sistema.

Conclusão

Está acima de um plano razoável? Sei que parte disso se resume a um julgamento com base em um entendimento mais completo do aplicativo em questão.


Eu acho que esta questão pode interess você stackoverflow.com/questions/105034/... Também esta leitura para mim como GUID, mas eles não parecem ser nativa em javascript
Rémi

2
Parece-me que os UUIDs já atendem aos 5 requisitos listados. Por que eles são insuficientes?
Gabe

@Gabe Veja meus comentários sobre a publicação de lie ryans abaixo
herbrandson

meta discussão sobre esta questão: meta.stackoverflow.com/questions/251215/...
mosquito

"cliente malicioso / rouge" - desonesto.
David Conrad

Respostas:


4

Sua abordagem vai funcionar. Muitos sistemas de gerenciamento de documentos usam esse tipo de abordagem.

Uma coisa a considerar é que você não precisa usar o uuid do usuário e o ID do item aleatório como parte da string. Em vez disso, você pode fazer o hash da concatinação de ambos. Isso fornecerá um identificador mais curto e possivelmente outros benefícios, porque os IDs resultantes serão distribuídos de maneira mais uniforme (melhor balanceado para indexação e armazenamento de arquivos se você estiver armazenando arquivos com base no uuid).

Outra opção que você tem é gerar apenas um uuid temporário para cada item. Então, quando você os conecta e os publica no servidor, o servidor gera (garantido) uuids para cada item e o devolve. Você atualiza sua cópia local.


2
Eu tinha considerado usar um hash dos 2 como um id. No entanto, ele não pareceu-me que havia uma forma adequada para gerar um sha256 em todos os navegadores que eu preciso apoio :(
herbrandson

12

Você precisa separar as duas preocupações:

  1. Geração de ID: o cliente deve ser capaz de gerar um identificador exclusivo no sistema distribuído
  2. Preocupação com segurança: o cliente DEVE ter um token de autenticação de usuário válido E o token de autenticação é válido para o objeto que está sendo criado / modificado

Infelizmente, a solução para esses dois está separada; mas felizmente eles não são incompatíveis.

A preocupação com a geração de ID é facilmente resolvida com a geração com UUID, é para isso que o UUID é projetado; a preocupação de segurança, no entanto, exigiria que você verifique no servidor se o token de autenticação fornecido está autorizado para a operação (ou seja, se o token de autenticação for para um usuário que não possui a permissão necessária para esse objeto em particular, DEVE ser rejeitado).

Quando tratada corretamente, a colisão não seria realmente um problema de segurança (o usuário ou o cliente simplesmente será solicitado a repetir a operação com outro UUID).


Este é realmente um bom ponto. Talvez isso seja tudo o que é necessário e eu estou pensando demais. No entanto, tenho algumas preocupações com essa abordagem. O maior é que os uuids gerados por javascript não parecem ser tão aleatórios quanto se poderia esperar (veja os comentários em stackoverflow.com/a/2117523/13181 para obter as detenções). Parece que o window.crypto deve resolver esse problema, mas isso não parece estar disponível em todas as versões do navegador que eu preciso oferecer suporte.
precisa saber é o seguinte

continuação ... Gostei da sua sugestão de adicionar código no cliente que regeneraria um novo uuid em caso de colisão. No entanto, parece-me que isso reintroduz a preocupação que tive no meu post no ponto 1 da seção "Quais são as vantagens dessa abordagem". Eu acho que se eu fui por esse caminho, eu estaria melhor fora apenas a geração de ID do temporária no lado do cliente e, em seguida, atualizá-los com o "id final" do servidor uma vez conectado
herbrandson

continuou novamente ... Além disso, permitir que os usuários enviem seus próprios IDs exclusivos parece uma preocupação de segurança. Talvez o tamanho de um fluido e a alta improbabilidade estatística de uma colisão sejam suficientes para atenuar essa preocupação por si só. Não tenho certeza. Eu tenho uma suspeita persistente de que manter cada usuário em sua própria "caixa de areia" é apenas uma boa idéia neste caso (ou seja, não confie em nenhuma entrada do usuário).
precisa saber é o seguinte

@ herbrandson: Não há nenhum problema de segurança que eu possa pensar ao permitir que os usuários gerem seu próprio ID exclusivo, desde que você sempre verifique se o usuário tem permissões para a operação. O ID é apenas algo que pode ser usado para identificar objetos, não importa realmente qual é o seu valor. O único dano potencial é que o usuário pode reservar um intervalo de IDs para seu próprio uso, mas isso não representa realmente nenhum problema para o sistema como um todo, porque é improvável que outros usuários cheguem a esses valores apenas por acaso.
Lie Ryan

Obrigado pelo seu feedback! Isso realmente me forçou a esclarecer meu pensamento! Há uma razão pela qual desconfiei de sua abordagem e a esqueci ao longo do caminho :). Meu medo está ligado ao pobre RNG em muitos navegadores. Para a geração de uuid, seria preferível um RNG criptograficamente forte. Muitos navegadores mais novos têm isso através do window.crypto, mas os navegadores mais antigos não. Devido a isso, é possível que um usuário mal-intencionado descubra a semente de outros usuários RNG e, assim, conheça o próximo uuid que será gerado. Esta é a parte que parece que poderia ser atacada.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.