Serviço da web RESTful - como autenticar solicitações de outros serviços?

117

Estou projetando um serviço da web RESTful que precisa ser acessado pelos usuários, mas também por outros serviços e aplicativos da web. Todas as solicitações recebidas precisam ser autenticadas. Toda a comunicação ocorre por HTTPS. A autenticação do usuário vai funcionar com base em um token de autenticação, adquirido pelo POST do nome de usuário e senha (em uma conexão SSL) para um recurso / sessão fornecido pelo serviço.

No caso de clientes de serviço da Web, não há usuário final por trás do serviço de cliente. As solicitações são iniciadas por tarefas agendadas, eventos ou algumas outras operações do computador. A lista de serviços de conexão é conhecida de antemão (obviamente, eu acho). Como devo autenticar essas solicitações provenientes de outros serviços (web)? Quero que o processo de autenticação seja o mais fácil possível de implementar para esses serviços, mas não à custa da segurança. Qual seria o padrão e as melhores práticas para um cenário como este?

Opções que posso pensar (ou que me foram sugeridas):

  1. Faça com que os serviços ao cliente recorram a um nome de usuário e senha "falsos" e os autentique da mesma forma que os usuários. Eu não gosto dessa opção - ela simplesmente não parece certa.

  2. Atribua um ID de aplicativo permanente para o serviço do cliente, possivelmente uma chave de aplicativo também. Pelo que entendi, isso é o mesmo que ter nome de usuário + senha. Com essa id e chave, posso autenticar cada solicitação ou criar um token de autenticação para autenticar outras solicitações. De qualquer forma, não gosto dessa opção, porque qualquer pessoa que conseguir o ID e a chave do aplicativo pode se passar pelo cliente.

  3. Eu poderia adicionar uma verificação de endereço IP à opção anterior. Isso tornaria mais difícil realizar solicitações falsas.

  4. Certificados de cliente. Configurar minha própria autoridade de certificação, criar certificado raiz e criar certificados de cliente para os serviços de cliente. No entanto, algumas questões vêm à mente: a) como eu ainda permito que os usuários se autentiquem sem certificados eb) quão complicado é este cenário de implementar do ponto de vista do serviço ao cliente?

  5. Outra coisa - deve haver outras soluções por aí?

Meu serviço estaria rodando em Java, mas eu deliberadamente deixei de fora informações sobre em qual framework específico ele seria construído, porque estou mais interessado nos princípios básicos e não tanto nos detalhes de implementação - presumo que a melhor solução para isso será ser possível implementar independentemente da estrutura subjacente. No entanto, sou um pouco inexperiente neste assunto, portanto, dicas e exemplos concretos sobre a implementação real (como bibliotecas de terceiros úteis, artigos, etc.) também serão muito apreciados.

Tommi
fonte
Se eu puder sugerir, familiarize-se com os serviços de site de grande porte e escolha o que você gosta. Seus usuários também descobririam semelhanças com as práticas recomendadas de outros serviços RESTful.
Yzmir Ramirez
Encontrada outra pergunta (quase dois anos) que toca um assunto semelhante: stackoverflow.com/questions/1138831/…
Tommi
Em qual sistema operacional os serviços (web e outros) estão hospedados? Eles estão sendo executados em servidores que fazem parte da mesma infraestrutura?
Anders Abel
O sistema operacional pode variar: Win, * nix etc. E os serviços do cliente podem ou não estar na mesma infraestrutura do meu serviço.
Tommi

Respostas:

34

Qualquer solução para esse problema se resume a um segredo compartilhado. Também não gosto da opção de nome de usuário e senha embutidos em código, mas tem a vantagem de ser bastante simples. O certificado do cliente também é bom, mas é realmente muito diferente? Há um certificado no servidor e outro no cliente. Sua principal vantagem é que é mais difícil usar a força bruta. Espero que você tenha outras proteções para se proteger contra isso.

Não acho que seu ponto A para a solução de certificado de cliente seja difícil de resolver. Você acabou de usar um galho. if (client side certificat) { check it } else { http basic auth }Não sou um especialista em java e nunca trabalhei com ele para fazer certificados do lado do cliente. No entanto, um rápido Google nos leva a este tutorial que parece exatamente o seu caminho.

Apesar de toda essa discussão sobre "o que é melhor", deixe-me apenas salientar que há outra filosofia que diz: "menos código, menos inteligência é melhor". (Eu pessoalmente mantenho essa filosofia). A solução de certificado de cliente parece muito código.

Sei que você expressou dúvidas sobre o OAuth, mas a proposta do OAuth2 inclui uma solução para seu problema chamada " tokens de portador ", que deve ser usado em conjunto com SSL. Acho que, por uma questão de simplicidade, eu escolheria o usuário / senha codificado (um por aplicativo para que possam ser revogados individualmente) ou os tokens de portador muito semelhantes.

newz2000
fonte
27
Os certificados do cliente NÃO são um segredo compartilhado. É por isso que eles existem. O cliente possui uma chave privada e o servidor possui uma chave pública. O cliente nunca compartilha seu segredo e a chave pública não é um segredo.
Tim
5
O link do tutorial não leva a um artigo do tutorial, mas a alguma página de índice Java no site da Oracle ...
Marjan Venema
2
@MarjanVenema Bem, isso é porque você está tentando o link +2 anos após newz2000 respondido, mas você sempre pode tentar o WayBack Machine: web.archive.org/web/20110826004236/http://java.sun.com/…
Fábio Duque Silva
1
@MarjanVenema: Sinto muito, mas você espera que o newz2000 venha aqui e atualize o link depois que ele for desativado? Como você disse, é uma podridão de link, então isso acontece mais cedo ou mais tarde. Ou você tenta acessar o arquivo para ver o que o autor estava vendo naquele momento, ou encontra o novo link e dá uma contribuição positiva. Não vejo como seu comentário ajudou alguém. Mas aqui, siga este link: oracle.com/technetwork/articles/javase/… (cuidado que eventualmente apodrecerá também)
Fábio Duque Silva
2
@ FábioSilva: Não. Não espero que ele faça isso. Eu li o arquivo, mas não tive tempo de procurar um novo link, então fiz a segunda melhor coisa: colocar um comentário informando que ele está morto para que outra pessoa da comunidade pudesse encontrar o novo local e atualizar o postar. Como você obviamente teve tempo para encontrar o novo link, por que não atualizou o link na postagem em vez de colocá-lo em um comentário reclamando para mim?
Marjan Venema
36

Depois de ler sua pergunta, eu diria, gere token especial para fazer a solicitação necessária. Este token viverá em um tempo específico (digamos, em um dia).

Aqui está um exemplo de para gerar token de autenticação:

(day * 10) + (month * 100) + (year (last 2 digits) * 1000)

por exemplo: 3 de junho de 2011

(3 * 10) + (6 * 100) + (11 * 1000) = 
30 + 600 + 11000 = 11630

em seguida, concatene com a senha do usuário, por exemplo "my4wesomeP4ssword!"

11630my4wesomeP4ssword!

Em seguida, faça MD5 dessa string:

05a9d022d621b64096160683f3afe804

Quando você chama uma solicitação, sempre use este token,

https://mywebservice.com/?token=05a9d022d621b64096160683f3afe804&op=getdata

Esse token é sempre único todos os dias, então acho que esse tipo de proteção é mais do que suficiente para sempre proteger nosso serviço.

Espero que ajude

:)

Kororo
fonte
1
Eu realmente gosto da maneira como você anexou o token de segurança em cada solicitação, mas o que acontece quando o programador já criou 100 páginas jsp e depois disso tem que implementar a segurança nas 100 páginas criadas anteriormente, bem como nas páginas que serão criadas. Nesse caso, anexar token em cada solicitação não é uma escolha correta. De qualquer forma, +1 para sua técnica. :)
Ankur Verma
4
O que acontece se os relógios não estiverem sincronizados? O cliente não gerará o token errado nesta instância? Mesmo se ambos gerassem a data e hora em UTC, seus relógios ainda poderiam ser diferentes, resultando em uma janela de tempo todos os dias, quando o token não funcionaria?
NickG de
@NickG, eu tive esse problema antes, a única maneira de garantir isso solicitando o tempo do servidor. Isso vai 99% matar o problema da UTC. Claro que a desvantagem é uma chamada extra para o servidor.
Kororo de
mas eu ainda posso consumir seu serviço da web usando esse token por um dia, certo? Não vejo como isso vai ajudar
Mina Gabriel
@MinaGabriel você pode adicionar mais tempo na geração de tokens. (minuto * 10) + (hora * 100) + (dia * 1000) + (mês * 10000) + (ano (últimos 2 dígitos) * 100000)
Kororo
11

Existem várias abordagens diferentes que você pode adotar.

  1. Os puristas RESTful vão querer que você use a autenticação BASIC e envie credenciais em cada solicitação. Seu raciocínio é que ninguém está armazenando nenhum estado.

  2. O serviço de cliente pode armazenar um cookie, que mantém um ID de sessão. Pessoalmente, não acho isso tão ofensivo quanto alguns dos puristas de quem ouço - pode ser caro autenticar continuamente. Parece que você não gosta muito dessa ideia.

  3. Pela sua descrição, realmente parece que você pode estar interessado no OAuth2. Minha experiência até agora, pelo que tenho visto, é que é meio confuso e meio sangrento. Existem implementações por aí, mas são poucas e raras. Em Java, eu entendo que ele foi integrado aos módulos de segurança do Spring3 . (O tutorial deles está bem escrito.) Estou esperando para ver se haverá uma extensão no Restlet , mas até agora, embora tenha sido proposta e possa estar na incubadora, ainda não foi totalmente incorporada.

jwismar
fonte
Não tenho nada contra a opção 2 - acho que é uma boa solução em um aplicativo RESTful - mas de onde o serviço de cliente obtém o token em primeiro lugar? Como eles se autenticam pela primeira vez? Talvez eu esteja pensando errado, mas parece estranho que o serviço de atendimento ao cliente precise ter seu próprio nome de usuário e senha para isso.
Tommi
Se o usuário final for um usuário seu, o serviço intermediário pode passar suas credenciais para você na primeira solicitação e você pode retornar um cookie ou outro token.
jwismar
Da mesma forma, no cenário OAuth, o usuário final está delegando ao serviço intermediário seu acesso ao serviço da web.
jwismar
Parece haver um mal-entendido - não há nenhum usuário final por trás do serviço ao cliente . Eu atualizei minha pergunta para explicar melhor a situação.
Tommi
1
Gostaria apenas de acrescentar que a opção nº 1 listada acima só deve ser feita por HTTPS.
sr-sk
3

Acredito na abordagem:

  1. Primeiro pedido, o cliente envia id / senha
  2. ID / senha de troca por token exclusivo
  3. Valide o token em cada solicitação subsequente até que expire

é bastante padrão, independentemente de como você implementa e outros detalhes técnicos específicos.

Se você realmente quiser ir além, talvez possa considerar a chave https do cliente em um estado temporariamente inválido até que as credenciais sejam validadas, limitar as informações se nunca forem validadas e conceder acesso quando forem validadas, novamente com base na expiração.

Espero que isto ajude

Dynrepsys
fonte
3

No que diz respeito à abordagem de certificado de cliente, não seria terrivelmente difícil de implementar e, ao mesmo tempo, permitir a entrada de usuários sem certificados de cliente.

Se você de fato criar sua própria Autoridade de Certificação autoassinada e emitir certificados de cliente para cada serviço do cliente, terá uma maneira fácil de autenticar esses serviços.

Dependendo do servidor da web que você está usando, deve haver um método para especificar a autenticação do cliente que aceitará um certificado de cliente, mas não exigirá um. Por exemplo, no Tomcat ao especificar seu conector https, você pode definir 'clientAuth = want', em vez de 'true' ou 'false'. Em seguida, certifique-se de adicionar seu certificado CA autoassinado ao seu armazenamento confiável (por padrão, o arquivo cacerts no JRE que você está usando, a menos que tenha especificado outro arquivo na configuração do servidor da web), de modo que os únicos certificados confiáveis ​​seriam aqueles emitidos fora de seu CA auto-assinado.

No lado do servidor, você só permitiria o acesso aos serviços que deseja proteger se pudesse recuperar um certificado de cliente da solicitação (não nulo) e passaria em qualquer verificação de DN se preferir qualquer segurança extra. Para os usuários sem certificados de cliente, eles ainda poderão acessar seus serviços, mas simplesmente não terão certificados presentes na solicitação.

Em minha opinião, esta é a maneira mais 'segura', mas certamente tem sua curva de aprendizado e sobrecarga, portanto pode não ser necessariamente a melhor solução para suas necessidades.

bobz32
fonte
3

5. Outra coisa - deve haver outras soluções por aí?

Você está certo, existe! E é chamado de JWT (JSON Web Tokens).

JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define uma maneira compacta e independente para transmitir informações com segurança entre as partes como um objeto JSON. Essas informações podem ser verificadas e confiáveis ​​porque estão assinadas digitalmente. Os JWTs podem ser assinados usando um segredo (com o algoritmo HMAC) ou um par de chaves pública / privada usando RSA.

Eu recomendo fortemente olhar para JWTs. Eles são uma solução muito mais simples para o problema quando comparados com soluções alternativas.

https://jwt.io/introduction/

justin.hughey
fonte
1

Você pode criar Sessão no servidor e compartilhar sessionIdentre cliente e servidor com cada chamada REST.

  1. Primeiro pedido RESTO autenticar: /authenticate. Retorna a resposta (de acordo com o formato do cliente) com sessionId: ABCDXXXXXXXXXXXXXX;

  2. Armazenar esta sessionIdem Mapcom sessão real. Map.put(sessionid, session)ou usar SessionListenerpara criar e destruir chaves para você;

    public void sessionCreated(HttpSessionEvent arg0) {
      // add session to a static Map 
    }
    
    public void sessionDestroyed(HttpSessionEvent arg0) {
      // Remove session from static map
    }
    
  3. Obtenha o sessionid com cada chamada REST, como URL?jsessionid=ABCDXXXXXXXXXXXXXX(ou outra forma);

  4. Recupere HttpSessiondo mapa usando sessionId;
  5. Valide a solicitação para aquela sessão se a sessão estiver ativa;
  6. Envie uma resposta ou mensagem de erro.
Arviarya
fonte
0

Eu usaria se um aplicativo redirecionasse um usuário para o seu site com um parâmetro de id do aplicativo, uma vez que o usuário aprova a solicitação, gere um token exclusivo que é usado pelo outro aplicativo para autenticação. Desta forma, os outros aplicativos não estão lidando com as credenciais do usuário e outros aplicativos podem ser adicionados, removidos e gerenciados pelos usuários. O Foursquare e alguns outros sites autenticam dessa forma e é muito fácil de implementar como o outro aplicativo.

Devin M
fonte
Hmm, não tenho certeza se consegui acompanhar a explicação. De qual usuário estamos falando? Estou falando sobre a comunicação de um aplicativo com outro aplicativo. Suponho que você tenha entendido isso, mas ainda não consigo entender. O que acontece quando esse "token" expira, por exemplo?
Tommi
Bem, o token que você gera e envia de volta para o outro aplicativo é um token persistente, está vinculado ao usuário e ao aplicativo. Aqui está um link para foursquares docs developer.foursquare.com/docs/oauth.html e é basicamente oauth2, então procure por uma boa solução de autenticação.
Devin M de
Parece haver um mal-entendido - não há nenhum usuário final por trás do serviço ao cliente . A documentação do foursquare que você vinculou menciona brevemente o acesso sem usuário, portanto, foi pelo menos um pouco útil - obrigado! Mas ainda não consigo formar uma imagem completa de como isso funcionaria na realidade.
Tommi
Apenas gere as chaves para os aplicativos então, se tudo o que você está fazendo é permitir o acesso aos aplicativos, um application_id e application_key simples devem funcionar para autenticação. Se você quiser que eles sejam autenticados com um token, procure usar as opções de autenticação de token do devise, pois seria apenas um parâmetro passado com a solicitação de url para o seu aplicativo.
Devin M de
Mas isso não é exatamente o mesmo que o cenário de nome de usuário + senha = token de autenticação de sessão? O application_id e application_key não são apenas sinônimos para nome de usuário e senha? :) É perfeitamente normal se esta realmente for a prática padrão para uma situação como esta - como eu disse, sou inexperiente nisso - mas eu apenas pensei que poderia haver outras opções ...
Tommi
-3

Além da autenticação, sugiro que você pense no quadro geral. Considere tornar seu serviço RESTful de back-end sem qualquer autenticação; em seguida, coloque algum serviço de camada intermediária necessária de autenticação muito simples entre o usuário final e o serviço de backend.

Dagang
fonte
Entre o usuário final e o serviço de back-end? Isso não deixaria os serviços de cliente totalmente não autenticados? Não é o que eu quero. É claro que posso colocar essa camada intermediária entre meu serviço da Web e os serviços do cliente, mas isso ainda deixa a questão em aberto: qual seria o modelo de autenticação real?
Tommi
A camada intermediária pode ser um servidor web, como Nginx, você pode fazer autenticação lá. O modelo de autenticação pode ser baseado em sessão.
Dagang
Como tentei explicar em minha pergunta, não quero um esquema de autenticação baseado em sessão para esses serviços de cliente. (Por favor, veja minhas atualizações para a pergunta.)
Tommi
Eu sugiro que você use a lista branca de ip ou intervalo de ip em vez de nome e senha. Normalmente, o ip do serviço do cliente é estável.
Dagang