Melhor solução para autorizar que um usuário só pode modificar / agir com seus próprios recursos em uma API REST


8

Fundo:

Atualmente no processo de construção de uma API REST, usando o nó w / express e é consumido por um aplicativo móvel e, eventualmente, por um site (baseado em navegador moderno).

Estou tentando identificar a melhor maneira de autorizar a solicitação de atualização / ação de um usuário, para que eles só possam modificar seus próprios recursos. As ações ocorrerão a uma taxa semi-alta, daí a preocupação.

Nota: A propriedade das entidades não pode ser transferida neste caso de uso.

Soluções potenciais:

Solução : armazene e mantenha uma lista dos recursos de cada usuário em uma sessão do servidor apoiada por algo como reddis.

Preocupação (s) : A persistência de uma sessão do lado do servidor possui seu próprio conjunto de complexidades, dimensionadas especificamente com vários servidores. Isso também viola o REST. do-sessões-realmente-violar-restfulness para obter mais informações.

Solução: faça uma consulta de leitura antes da atualização / ação do usuário. O IE me fornece os itens deste usuário, se ele estiver na lista, continue com a atualização.

Preocupação (s): sobrecarga de uma leitura adicional toda vez que um usuário age em nome de um recurso.

Solução: passe o ID do usuário para a camada db e torne-o parte da atualização condicional ou, se desejar, use algo como a segurança no nível de linha do Postgres para esse recurso, dependendo do back-end de dados.

Preocupação (s): isso parece um pouco tardio no ciclo de vida da solicitação para verificar se o recurso é o usuário solicitante. O erro teria que ser lançado completamente do back-end de dados. Na mesma nota, também seria um pouco fora de lugar, considerando que a autenticação e a autorização baseada em função geralmente são feitas no início do ciclo de vida da solicitação. A implementação também depende do backend de dados. Ele também empurra a lógica de negócios no back-end de dados.

Solução: Sessão assinada do lado do cliente. Com um JWT ou um cookie criptografado / assinado. Manutenção essencial de uma sessão confiável contendo uma lista dos IDs de recursos do usuário.

Preocupação (s): O tamanho da sessão do lado do cliente. O fato de que ele seria enviado a cada solicitação, mesmo quando não for necessário. A manutenção se torna extremamente complexa quando você introduz a possibilidade de várias sessões / clientes ativos. Como você atualizaria o estado do lado do cliente quando um recurso foi adicionado em outro cliente.

Solução: passe um token de atualização assinado (JWT) ou URL para o cliente com o recurso quando o recurso for buscado. Espere que, quando um recurso seja atualizado / acionado. O token assinado conteria a identificação do usuário e a identificação do recurso e você poderá verificar facilmente com relação a elas.

Preocupação (s): fica complexa se a propriedade do recurso for transferível, mas no meu caso isso não é uma preocupação. Introduz complexidade sobre uma leitura antes da atualização. É um pouco estranho?

Pensamentos finais:

Estou me inclinando para a última solução, mas como não vejo isso acontecer com muita frequência, fico imaginando se estou perdendo alguma coisa. ou talvez seja parte de um padrão de design que eu não conheço.


1
Supondo que seja possível transferir a propriedade, a última abordagem implicaria a necessidade de verificar se esse usuário ainda é o proprietário do referido recurso. Então você acaba tendo o token mais uma das outras abordagens.
Miguel van de Laar

@MiguelvandeLaar Obrigado pela resposta! você está correto, no entanto, no meu caso de uso, a propriedade não pode ser transferida, sob nenhuma circunstância.
Ashtonian 29/03/16

2
"uma sessão do servidor" A menos que esteja faltando algo substancial, o REST impede sessões do lado do servidor.
Lightness Races em órbita

@ BarryTheHatchet - Parece que isso pode ser debatido, há uma discussão interessante aqui sobre isso: stackoverflow.com/questions/6068113/… . Essa solução também está em torno última vez que torna a introdução de nova carga divertido servidores equilibrada ..
Ashtonian

@Ashtonian: A resposta aceita 131 marcou a essa pergunta concorda comigo;) (como faz o seguimento 223-marcados)
Leveza raças em órbita

Respostas:


1

Eu acho que existem duas soluções na sua lista que se encaixam melhor no seu caso de uso; a consulta de leitura antes da atualização (solução 2) e o envio de um token de atualização com a solicitação de leitura (solução 5).

Se você estiver preocupado com o desempenho, eu escolheria uma ou outra, dependendo de quantas leituras versus atualizações de um recurso que você espera. Se você espera muito mais atualizações do que leituras, obviamente a solução 5 é melhor, porque você não precisa fazer uma busca adicional no banco de dados para descobrir o proprietário do recurso.

No entanto, você não deve esquecer as implicações de segurança de fornecer um token de atualização. Com a solução 2, assumindo que a autenticação é segura, a atualização de um recurso provavelmente também é segura porque você determina o proprietário de um recurso no servidor.

Com a solução 5, você não verifica a reivindicação feita pelo cliente, exceto pelo fato de verificar uma assinatura válida. Se você estiver permitindo que a atualização ocorra por um link de texto não criptografado (por exemplo, sem SSL), apenas a codificação do recurso e da identificação do usuário no token não é segura. Por um lado, você está se abrindo para repetir ataques, portanto, inclua um nonce que cresça a cada solicitação. Além disso, se você não codificar um carimbo de data / hora no token de atualização, basicamente concede acesso indefinido à atualização a qualquer pessoa que possua esse token. Por fim, se você não deseja o nonce, deve incluir pelo menos um hmac do recurso buscado, para que o token de atualização seja válido apenas para o estado do recurso conforme foi buscado. Isso dificulta os ataques de reprodução e limita ainda mais o conhecimento de danos do token de atualização,

Eu acho que mesmo se você estiver se comunicando por um link seguro, adicionar um nonce (ou pelo menos um hmac do estado do recurso) e uma data de validade para o token de atualização seria uma coisa inteligente a se fazer. Não conheço o domínio do aplicativo, mas você pode lidar com usuários mal-intencionados e eles podem causar todo tipo de estragos com o poder do acesso ilimitado à atualização de seus próprios recursos.

Adicionar um nonce irá significar uma coluna adicional por recurso em seu banco de dados onde você armazena o valor do nonce enviado com a última solicitação de busca. Você pode calcular o hmac sobre a representação json serializada do seu recurso. Você pode enviar seu token não como parte do corpo da mensagem, mas como um cabeçalho HTTP adicional.

Ah, e eu usaria um token, não um URL; no REST, o URL de atualização para um determinado recurso deve ser o mesmo URL do qual o recurso foi buscado, certo? Eu moveria todos os itens relacionados à autenticação e autorização para os cabeçalhos HTTP, por exemplo, você poderia fornecer o token de atualização no cabeçalho de autorização da solicitação PUT.

Observe que adicionar um nonce que cresça a cada busca de recursos também exigirá uma atualização do banco de dados (o novo valor para o nonce) para cada solicitação de busca de recursos (embora você possa se atualizar apenas com o nonce quando o estado do recurso for realmente alterado , portanto, seria livre do ponto de vista de desempenho), a menos que você queira manter essas informações na memória transitória e simplesmente peça aos clientes que tentem novamente quando o servidor for reiniciado entre uma busca e uma solicitação de atualização.

Uma observação à sua solução 4 (sessões do lado do cliente): Dependendo do número de recursos que seus usuários podem criar, o tamanho da sessão pode não ser um problema. O problema de atualização de sessão do lado do cliente também pode ser bastante fácil de solucionar: Se uma solicitação de atualização para um recurso falhar, porque o recurso não está listado nos dados da sessão que você recebeu do cliente, mas o usuário está correto, faça uma verifique no back-end se os dados da sessão desse cliente estão desatualizados. Se for, você permite a atualização e envia de volta um cookie atualizado com a resposta para a solicitação de atualização. Isso só causará uma pesquisa no banco de dados quando um usuário tentar atualizar um recurso de um cliente com dados desatualizados da sessão local (ou quando ele for malicioso e tentar fazer um ddo para você, mas a taxa é limitada ao resgate!). Contudo, Concordo que a solução 4 é mais complicada do que as outras e você é melhor com uma de suas outras idéias. A solução 4 também possui várias considerações de segurança que você deve levar em consideração; armazenar esse estado de autorização no cliente realmente precisa que sua segurança seja estanque.


E se todos os recursos pertencentes ao usuário forem modelados como recursos filho. (Por exemplo, / users / 1 / entity / 2). Combine isso com um JWT assinado que tenha o ID do usuário. Se você fizer isso, a autorização poderá até ser delegada em um servidor separado (API Gateway) porque não precisa acessar o banco de dados.
Chamindu 08/07/19

0

Outra solução que você pode considerar:

Se um recurso realmente não puder ser transferido para outro usuário depois de criado, você pode pensar em codificar o ID do usuário.

Por exemplo, todos os recursos pertencentes ao usuário 'alice' são nomeados 'alice-1', 'alice-2' etc.

É então trivial verificar se 'eve' tem acesso a um recurso chamado 'alice-1'.

Se você não deseja que a propriedade do recurso seja de conhecimento público, você pode usar uma função de hash criptográfico da seguinte forma:

  1. Dada uma senha, apenas seu servidor sabe
  2. E um nome de usuário u (por exemplo, 'alice')
  3. E um nome de recurso r (por exemplo, '1')

Gerar hmac(password, u | '-' | r ), onde | é concatenação. Use o resultado, juntamente com o nome do recurso r, como o ID do recurso. Opcionalmente, adicione um valor salt (como para armazenar senhas).

Quando uma solicitação de atualização vem do usuário 'alice' para um determinado ID de recurso, você extrai r do ID de recurso, calcula hmac (senha, 'alice' | '-' | r) e o compara com a parte restante do ID de recurso . Se corresponder, alice poderá atualizar o recurso; se não corresponder, ela não está autorizada. E sem a senha, ninguém pode descobrir quem possui qual recurso.

Eu acho que é seguro, mas você pode querer aprimorar as qualidades dos hmacs.


0

Parece haver dois problemas separados que você precisa gerenciar. Eles são (1) Como você está autenticando o usuário que está gerando a solicitação? e (2) como você sabe quais objetos de back-end estão associados a esse usuário. Também tenho uma observação que pode ser adequada ao seu caso de uso (3)

(1) A autenticação do usuário de maneira RESTful nunca é bonita. Eu prefiro usar os cabeçalhos de autenticação / autorização HTTP e as respostas HTTP 401 para acionar a autenticação, o que significa que você está potencialmente autenticando o usuário em todas as solicitações. Por se tratar de um aplicativo móvel, você tem a opção de criar seu próprio esquema de autenticação personalizado, que pode permitir que você forneça um token de sessão em vez de detalhes completos de autenticação em solicitações futuras.

(2) O usuário associado a um conjunto de objetos certamente é um dado importante que você precisa armazenar juntamente com os objetos reais em seu armazenamento de dados. Como parte da lógica de negócios, ele precisa verificar quem é o proprietário do objeto e compará-lo à identidade que está acessando o objeto. Eu faria isso explicitamente no código, como parte da camada de negócios que valida a solicitação. Se as ocorrências no banco de dados forem um problema grande o suficiente, faria sentido armazenar essas informações em cache em um armazenamento de valores-chave, como memcache ou (para volumes muito altos, sites altamente disponíveis, riak) e atingir o banco de dados somente se o cache estiver frio para essa combinação de usuário / objeto.

(3) Se você tiver um número pequeno de usuários e objetos, poderá combinar o ID do usuário e do objeto no mesmo campo e usá-lo como a chave primária do banco de dados da tabela de objetos. por exemplo, o número de 64 bits fornece espaço de 24 bits para o ID do usuário e 40 bits de espaço para os IDs de objetos. Dessa forma, quando você está consultando o objeto, pode ter 100% de certeza de que é para o seu usuário.

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.