Morte à sessão! Entenda esse tal de stateless session com tokens

Microservices pelo visto já é uma realidade, há inúmeros artigos falando sobre servidores, implementações, arquitetura. Daqui a pouco começam a surgir artigos falando mal ou que o conceito está morto. Enfim, você provavelmente já sabe o ciclo de vida de uma BuzzWord. Voltando, eu não consigo desassociar MicroServices de um outro conceito que já foi uma buzzword há um tempo atrás. REST.

Demorei anos para entender o conceito, algo que o Guilherme Silveira já falava lá em 2009 como hipermída (HATEOAS), 2010 como diminuir acoplamento, só fui compreender há pouco tempo. E um desses conceitos sempre foi para mim obscuro demais: RESTFul API, mais especificamente: ser stateless. Portanto, é esse conceito que eu vou tentar ajudar você a entender também.

Essa dúvida não é só minha: “como fugir da famosa Session em um contexto RESTFul API?“. Aposto que foi um dos primeiros conceitos que você aprendeu ao lidar com programação web, que existe a possibilidade de ter um espaço alocado no servidor para cada cliente e nele você pode guardar qualquer informação relevante para o atendimento deste. Então, surge esse conceito de RESTFul API e diz que sessão na verdade não é uma boa ideia?!?!?!?! Por que isso?

vamos puxar da raiz…

Segundo Roy Fielding, ser stateless é a capacidade que um servidor tem de processar requisições de um cliente sem precisar usufruir de nenhum dado já previamente guardado nele. Logo, o envio de dados ao servidor deve conter TODA informação necessária para ser compreendida e processada. Em suma, o estado quem mantém é o cliente e não o servidor.

Agora, novamente, por que isso?

algumas respostas para isso, mas uma delas poderia ser quanto à escalabilidade.

imaginando o cenário…

Se o servidor cai ou aumenta o número de clientes, qual seria a solução?
Ok, colocar mais um servidor e um load balancer na frente.

Agora temos um problema. O servidor 1 contém os dados do cliente, se ele cai você precisa redirecionar as requisições para o servidor 2 (ok, load balancer já faz isso). Além disso, você precisa também pegar todos os dados que estavam no servidor 1, passivá-los e transferí-los para o servidor 2. ANTES DELE CAIR!!!

Agora imagina que o seu load balancer possui um algoritmo que alterna as requisições entre servidores ao invés de fazer o stick session. A primeira é o servidor 1 que responde, a segunda é o servidor 2. E agora? A sessão do seu usuário tem que ficar trafegando entre servidores. Essa sincronia começa a ficar complicada.  Logo, talvez a melhor solução, seja simplesmente evitar o problema.
Aqui o peso de manter uma sessão no servidor começa a ser cobrado. O que você, eu e muuuuuuuuiiiiiiiittaaa gente acaba fazendo é simplesmente migrar o usuário para o servidor 2 sem os dados prévios. Ou seja, o usuário não “percebe” que o serviço não ficou disponível.

Perdemos aqui um item primordial, Escalabilidade horizontal. À medida que o número de clientes aumenta, são adicionados novos servidores para permitir que eles atendam à demanda crescente. E, de fato, é um pouco mais complicado de se implementar quando estamos em um arquitetura stateful.

Portanto, compreendemos o porquê de um sistema RESTFul ser totalmente stateless. As informações devem ficar no cliente e não no servidor. Por conseguinte, podemos aumentar o número de servidores e cada um será capaz de responder por novas requisições sem se preocupar em buscar dados anteriores, afinal, o cliente já vai fornecer as credenciais de quem ele é e outras informações adicionais.

frameworks podem e vão ajudar…

A resposta você provavelmente já sabe. Token! Já, inclusive, falamos sobre OAuth em um contexto MicroServices Rest. Uma outra solução seria o Json Web Token, que é um padrão aberto para representação das informações que duas partes (cliente e servidor) acordaram previamente. De fácil instalação, manipulação (fluent interface API), implementação em múltiplas linguagens (.NET, Java, Phyton) e inúmeros artigos falando sobre o tema.

O conceito básico é: você como usuário manda sua autenticação para o servidor. Ele valida e identifica seus papéis e gera um token com essas informações para o cliente guardar. (Cookie X HTML 5 web storage). Quando requisições são feitas ao servidor, o token é enviado e as informações são recuperadas.

Para isso o token deve ser assinado com uma chave, que deve ficar no servidor. Se você precisar subir mais servidores, eles devem ter a mesma chave.


//salvando a String em um arquivo, ele poderá ser compartilhado entre servidores.
 String chaveAsString = Base64.getEncoder().encodeToString(MacProvider.generateKey().getEncoded());

//recuperando a chave do arquivo
 SecretKey chave;
 String nextLine = new Scanner(file).nextLine();
 byte[] chaveEmBytes = Base64.getDecoder().decode(nextLine);
 chave = new SecretKeySpec(chaveEmBytes, SignatureAlgorithm.HS512.getJcaName());

Nosso famoso token…

Ao gerar o token você escolhe o conteúdo, algoritmo, tempo de expiração, dentre outros. O conteúdo precisa ser uma String, eu costumo parsear para Json.

public String gerar(Object objeto) {
   String objetoAsJson = new Gson().toJson(objeto);
   return Jwts.builder().setSubject(objetoAsJson).signWith(SignatureAlgorithm.HS512, chave).compact();
}

public <T> getConteudo(String token, Class<T> clazz) {
   try {
      String conteudo = Jwts.parser().setSigningKey(chave).parseClaimsJws(token).getBody().getSubject();
      return ((T) new Gson().fromJson(conteudo, clazz));
   }catch (io.jsonwebtoken.SignatureException | io.jsonwebtoken.ExpiredJwtException e) {
      throw new TokenInvalidoException(e);
   }
}

E o Logoff?

O token ficará válido pelo tempo definido no setExpiration. Todavia, o usuário pode invalidar o token antes do prazo (logout). Como fazer isso? A forma mais fácil seria apenas remover o token do cliente. Se ele está guardado em um Cookie, basta remové-lo.

Mas isso não resolve o problema de alguém ainda ter guardado o token em outro lugar e usá-lo para obter informações do servidor. O que nos leva a uma segunda solução: black list de tokens. Nesse contexto, teria que existir uma lista guardada em memória(Redis, MemCached, Bolt DB), compartilhada entre os servidores,  que seria consultada antes de cada requisição.

Houve essa discussão recentemente na nossa lista interna e com certeza esse é um dos pontos mais chatos, invalidar o token.

Por fim, uma outra decisão arquitetural que precisará ser discutida pela equipe é se todos os servidores vão saber descriptografar o token ou se existirá um servidor central responsável por essa abordagem. Prós e contras em ambas decisões como evitar espalhar código, ponto único de falha, requisições duplicadas (uma requisição sempre para o servidor de autenticação). E se o servidor de autenticação cair?????? E se a forma de lidar com o token precisar ser alterada??? Eu gostei bastante do JWT e achei mais fácil de lidar do que o OAuth. E você, já implementou sua API RESTFul totalmente stateless?

19 Comentários

  1. Ribeiro 03/04/2017 at 13:33 #

    Eu sempre fui meio cético com o JWT por esses dois problemas: invalidar todas as sessões de um usuário e se proteger contra replay attack. Ambas as soluções envolvem consultar um cache em memória ou banco de dados. Mas o curioso é que isso invalida justamente a proposta principal de ser stateless.

    Se é pra consultar o MemCached ou seu DB, por que não simplesmente persistir um token randómico e fazer a verificação a cada request?

  2. Rafael Ponte 03/04/2017 at 16:16 #

    MUITO bacana, Rapha! Até hoje não precisei de stateless session, então nunca me aprofundei no assunto; seu post deu uma visão bacana e ainda deixou claro os principais problemas da abordagem.

    Já tinha lido sobre JWT mas como nunca precisei acabei só vendo por cima.

    O que o Ribeiro comentou é interessante: se é pra ser stateless não faz sentido manter estado num cache ou banco. A não ser claro, que consideremos stateless somente a aplicação, já que ela por si só não guardaria estado mas saberia onde encontrá-lo: banco, cache, disco etc.

    PS: ficou meio confuso sua explicação porque devemos ir pra stateless session, se puder da uma revidada nessa parte do artigo.

  3. Raphael Lacerda 03/04/2017 at 16:51 #

    Pois é Ribeiro… Invalida token é chato mesmo..

    Enfim, não se pode ter tudo né?

    @ponte pra mim a principal vantagem do stateless é justamente a do desenho. Escalabilidade horizontal

  4. Rafael Ponte 03/04/2017 at 21:28 #

    Sem duvida! Escalar e testar apps stateless é muito mais fácil do que stateful.

    Enfim, parabéns novamente pelo post!

  5. Helton 04/04/2017 at 03:46 #

    Ótimo post!

  6. Fernando Moraes 04/04/2017 at 16:09 #

    Juntamente com escalabilidade, adicionaria o fato de melhorar o deploy das aplicações, pois o deploy poderia ser feito a qualquer momento, sem “derrubar” nenhum usuário “pendurado” em alguma sessão.

    Sobre o assunto da autenticação/autorização, acredito que pra essa necessidade ainda existe o conceito de sessão, oque torna-se stateless é a aplicação mesmo.

  7. Alexandre Aquiles 06/04/2017 at 12:22 #

    Opa, Rapha! Muito bom o artigo!

    Dúvida: qual das bibliotecas você usou no exemplo?
    https://jwt.io/#libraries

    Pelo que pesquisei, foi a jjwt, né?
    https://github.com/jwtk/jjwt

  8. Anderson Jean Fraga 10/04/2017 at 20:17 #

    Realmente o maior problema é a sincronia de tokens entre os multiplos servidores da aplicação.
    Pela minha experiência, a utilidade do stateless é exatamente em aplicações restful-like, não havendo necessidade (ou, dependendo até, possibilidade) de gravar cookies no cliente.

    Ainda de acordo com minha experiência, até agora a maneira mais eficiente de manipular os tokens foi torná-los únicos ‘a vida inteira’ e compartilhar entre os multiplos servidores (seja via replicação do banco de dados, seja via cache). Rotinas de exclusão desses itens podem ser necessários, claro, para evitar o acumulo de lixo.

  9. Felipe Affonso 10/04/2017 at 22:44 #

    Excelente artigo.

    A parte de renovação de token também é bem chata, talvez você possa falar sobre isso em um próximo artigo.

    Parabéns!

  10. Raphael 11/04/2017 at 13:59 #

    exato
    https://github.com/jwtk/jjwt

  11. Victor Pinto 24/04/2017 at 15:48 #

    Imagine o cenário de ter dois ambientes, portal web e aplicativo mobile. O usuário teria um token pra cada. Se ele alterar a senha de login, acha que deveria também invalidar os tokens?

  12. Ribeiro 26/04/2017 at 11:31 #

    Posteriormente à saída do artigo, tive que estudar mais o JWT como mecanismo de autenticação já que era requisito para uma aplicação que eu estava desenvolvendo. Pois bem, concluí que ele definitivamente não deve ser usado para manter uma sessão longa no frontend (um usuário logado, por exemplo).

    Esse artigo explica muito bem os pontos contras: http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/

    Resumidamente:
    – O jwt pode ficar realmente grande dependendo dos dados (e ele deve ser enviado em todos os requests);
    – A impossibilidade de revocar tokens;
    – Os dados podem ficar inválidos (o usuário não é mais admin, mas existem tokens com esse claim que vão funcionar até expirar).

    Acho muito boa a sacada o JWT e acredito que tem casos – como o de autorização para ações únicas – que ele se encaixa muito bem. Porém, para o caso de autenticação longa, me parece que ele não é uma boa solução. Infelizmente isso está sendo recomendado indiscriminadamente por aí.

  13. Jefferson 03/05/2017 at 12:12 #

    Bom dia Raphael, ainda não precisei implementar servidores stateless mas tenho uma duvida. Vc falou sobre a dificuldade de invalidar um token, q o cara apesar de dar “logout” e vc apagar o token, o cara pode ter copiado o token e pode fazer um ataque de replay (q me lembra playcenter rs).
    Mas vc tb disse q vc precisa distribuir as chaves (privadas) pelos servidores. No Logout se vc apagar essas chaves (privadas) da msm forma q vc as cria, o token não terá validade do msm jeito! Correto?
    Abrassss

  14. Raphael 05/05/2017 at 09:59 #

    Jefferson, mas se vc apaga a chave privada, já era todos os tokens que foram gerados com ela
    =/

  15. Fernando Franzini 24/05/2017 at 09:16 #

    Os dados da sessão vão para o cliente aonde? Heap do javascript? web storage?

  16. Daniel 25/05/2017 at 18:02 #

    Raphael, deixa eu ver se entendi. Imagina um sistema de e-commerce onde as informações do carrinho de compras são armazenadas na sessão em uma ambiente stateful, nesse caso eu teria que ter um mecanismo de transferir a sessão para outra máquina caso quisesse uma escalabilidade horizontal com um balanceador de cargas na frente. Nessa sua abordagem todas essas informações seriam armazenadas no lado do cliente? Não entendi muito bem, eu teria que replicar o modelo de dados(Produto, ItemProduto) que tenho no servidor para o lado cliente? Se não, como isso seria armazenado no cliente, de modo que eu pudesse recuperar a informação?

    Obrigado

  17. Josemar 08/06/2017 at 11:29 #

    Eu tenho uma dúvida com relação ao uso de tokens em api stateless. Digamos que eu tenha um WebService Rest e uma aplicação mobile que precise estar autenticada para consumir este webservice, ok autentico o usuário e se tudo der certo gero um token, mas e quando este token expirar, qual a abordagem mais indicada para que a aplicação mobile não fique solicitando ao usuário login e senha sempre que o token expirar?

Deixe uma resposta