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
- Os IDs devem ser globalmente exclusivos (ou pelo menos exclusivos dentro do sistema)
- Gerado no cliente (ou seja, via javascript no navegador)
- Seguro (conforme descrito acima e de outra forma)
- Os dados podem ser visualizados / editados por vários usuários, incluindo usuários que não os criaram.
- 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:
- Identifique isso como uma ação "inserir" (ou seja, não uma atualização ou exclusão)
- Validar que ambas as partes da chave composta são uuids válidos
- 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)
- 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?
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.
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.
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
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 )
Faltam algumas brechas que podem permitir que um usuário mal-intencionado comprometa o sistema?
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 )
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.
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.
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.