Blindando sua API com o padrão DTO

Blindando sua API com o padrão DTO

Falaaa galerinha blza? , pois bem, como estamos na era das API´s e MS, entendi que seria ideal falar um pouco sobre a manipulação de dados com o padrão DTO, até mesmo proteger sua API.

Quando se está criando uma API é de extrema importância não apenas pensar como um usuário regular irá interagir, mas também se defender contra usuários maliciosos para que eles não consigam causar nenhum prejuízo para a sua aplicação e para seus usuários.

Mãos a OBRA !

Temos uma API, escrita em Java, usando o framework Spring, que é usada pelos funcionários da nossa empresa para que eles possam “bater ponto”, ou seja, para informar o horário de entrada e saída dos mesmos.

Para que nossa aplicação funcione, é necessário que os funcionários façam um cadastro no sistema. Cada funcionário é representado pela seguinte classe:

Não foi fornecido texto alternativo para esta imagem

  • @Entity: Nossa classe Usuario é uma entidade que será mapeada no nosso banco de dados.
  • @Id/@GeneratedValue: O atributo anotado será a primary key da tabela e será gerado automaticamente usando a estratégia IDENTITY.
  • @Getter: Gera os bytecodes dos getters dos nossos atributos, basicamente temos os métodos getters sem escrevê-los.

Temos o nosso Controller:

Não foi fornecido texto alternativo para esta imagem






  • @RestController: Indica que este controller por padrão responderá usando, por padrão, o formato JSON.
  • @RequestMapping: Usamos para mapear as urls dos nossos métodos, neste caso, todos os métodos desse controller terão como base o “/usuarios”
  • @Autowired: Com essa anotação indicamos que os parâmetros do nosso construtor serão injetados
  • @PostMapping: Só mapeamos nosso método salvar. Este método será invocado quando a url: /usuarios, usando o método POST for acessada.
  • @RequestBody: Indicamos que o objeto usuario tem que ser buscado no corpo da requisição.

Para acessar nosso banco de dados temos a classe UsuarioRepository:

Não foi fornecido texto alternativo para esta imagem


@Repository: Faz o framework enxergar nossa classe e indicamos que se trata de um repositório, ou seja, uma classe que tem como única função acessar o banco de dados.

Vale uma explicação que, ao extender CrudRepository automaticamente herdamos vários métodos, que para nosso exemplo já serão suficiente.

Vamos olhar nossa classe de serviço e ver como ela realiza a tarefa de salvar um novo usuário:

Não foi fornecido texto alternativo para esta imagem






@Service: Usamos esta anotação para que o framework enxergue nossa classe e indicamos que esta classe é um serviço.

Parece simples, recebemos um usuário como parâmetro e devolvemos o usuário criado. O método save() nos devolve o objeto salvo, logo salvamos e já retornamos o objeto.

Nada mais justo que testarmos nossa aplicação, certo?

Para isso vamos usar o Postman e enviar uma requisição para nosso método salvar:

Não foi fornecido texto alternativo para esta imagem

Enviando esta requisição, temos a seguinte resposta:

Não foi fornecido texto alternativo para esta imagem

Funcionou perfeitamente, o objeto foi salvo! Podemos perceber isso pelo atributo id, vejam que ele foi gerado automaticamente.

Vamos fazer um outro teste. E se eu mandasse esta requisição:

Não foi fornecido texto alternativo para esta imagem

Nesta nova requisição estou enviando um parâmetro extra, estou enviando o atributo admin passando o valor true.

Pensando em como o sistema deve funcionar ele não deveria permitir isso, certo? Fornecer permissões de administrador para um usuário deve ser uma função separada com sua própria lógica, portanto, nosso método que salva um usuário não deve permitir tal parâmetro. Vamos ver:

Não foi fornecido texto alternativo para esta imagem

Vish… acabamos de criar um usuário com permissões de administrador. Isso não é bom.

Esse é um problema que algumas aplicações possuem por receber o objeto real diretamente como parâmetro do método que recebe a requisição, no nosso caso, o salvar() do nosso controller.

O Jackson, framework usado para serialização/desserialização, compara os atributos do JSON com os atributos da classe do objeto como parâmetro do método que está recebendo a requisição, no caso, o objeto usuario.

Com isso, basicamente, todos os atributos do JSON que tiverem um correspondente do mesmo nome na classe Usuario serão preenchidos.

Pensando numa aplicação real que possui este mesmo contexto, quão difícil seria para um usuário malicioso descobrir que tal classe possui um atributo booleano chamado admin?

Ao invés do Postman, em um formulário de cadastro, quão difícil seria adicionar um input com o tipo hidden passando admin igual a true?

Se “googlarmos” um pouco a respeito, vamos achar algumas notícias falando que tal empresa foi invadida usando exatamente esta técnica.

Como nos proteger disso?

Aplicando o padrão DTO

Podemos usar um conhecido padrão de projeto chamado DTO (Data Transfer Object), que basicamente é uma classe com atributos simples, que usamos para otimizar a comunicação entre o client e o servidor.

Nossa classe DTO pode receber atributos e, assim, podemos manipulá-los da forma que quisermos.

Para melhor compreensão vamos criar a classe UsuarioDTO e vamos recebê-la como parâmetro do método salvar() da classe UsuarioController:

Não foi fornecido texto alternativo para esta imagem


UsuarioController:

Não foi fornecido texto alternativo para esta imagem



Fazendo isso recebemos erro de compilação pois o método salvar do nosso serviço precisa receber um objeto do tipo Usuario e não UsuarioDTO. Depois arrumamos isso.

Perceba que agora a desserialização do JSON ocorrerá com a classe UsuarioDTO, logo precisamos escolher quais atributos queremos receber. Basicamente queremos todos, menos o admin e o id, certo? Então vamos adicionar os atributos escolhidos:

Não foi fornecido texto alternativo para esta imagem




Agora precisamos passar objeto do tipo Usuario para o método do nosso serviço e não um UsuarioDTO. Vamos ensinar nossa classe UsuarioDTO a criar um objeto do tipo Usuario. Primeiro vamos criar um construtor na classe Usuario:

Não foi fornecido texto alternativo para esta imagem



Acabamos de sobrescrever o construtor padrão da classe, então, devemos adicionar um construtor vazio para que o Hibernate não reclame na hora que formos fazer operações com o banco de dados.

Não foi fornecido texto alternativo para esta imagem




@NoArgsContructor: Adiciona um construtor vazio.

Agora vamos criar um método na classe UsuarioDTO que transforma o DTO em um objeto do tipo Usuario:

Não foi fornecido texto alternativo para esta imagem



Simples não? Só nos resta invocar este método no nosso controller, para que, assim, passemos um objeto do tipo Usuario para o método do serviço:

Não foi fornecido texto alternativo para esta imagem



Perceba que agora, mesmo que o usuário tente enviar um atributo admin não irá funcionar, pois nossa classe UsuarioDTO não possui este atributo. Vamos testar:

Não foi fornecido texto alternativo para esta imagem

Novamente estamos enviando o atributo admin passando o valor true. Como resposta dessa requisição, temos:

Não foi fornecido texto alternativo para esta imagem

Funcionou! Olha o atributo admin com valor false.

Tem outra coisa que está me incomodando, é estranho retornar a senha, não? Normalmente a senha é encriptada e salva no banco de dados, não deveríamos enviar este atributo que em teoria deve ser bem protegido.

Por outro lado, é imprescindível retornar as informações do usuário que acabamos de criar.

Usando o padrão DTO para customizar respostas HTTP

Podemos criar um DTO de resposta também. Criamos uma classe com os atributos que queremos enviar para o nosso client e transformamos o usuário criado nesse DTO de resposta.

No caso queremos todos os atributos menos a senha, certo? Então, primeiro colocaremos todos os atributos que queremos retornar nessa nova classe:

Não foi fornecido texto alternativo para esta imagem





  • @AllArgsContructor: Cria um construtor privado com todos os atributos.

Agora criamos um método que recebe um objeto do tipo Usuario e retorna esta própria classe:

Não foi fornecido texto alternativo para esta imagem

E finalmente, no método do nosso controller, só nos resta transformar nosso objeto usuario em um objeto do tipo UsuarioRespostaDTO e mudar o tipo englobado pelo ResponseEntity:

Não foi fornecido texto alternativo para esta imagem

Testando nosso DTO de resposta, temos:

Não foi fornecido texto alternativo para esta imagem

Pronto, agora a senha não está mais sendo enviada.

O padrão de projeto DTO é muito útil tanto para receber dados quanto para enviá-los, pois podemos manipular da forma que quisermos tais dados para facilitar a comunicação entre o servidor e o client.

Caso tenha ficado alguma dúvida ou tenha alguma sugestão, por favor comente aqui, tentarei responder o mais rápido possível.

Jorge David Jr

Desenvolvedor Java Back-End na PrimeIT

2 a

Parabens pelo conteudo

Victor M.

DevOps | CI/CD | Docker | Linux | Kubernetes | Github Actions | Azure | ArgoCD

3 a

Conteúdo agregador, vlw Edilson Freitas

Entre para ver ou adicionar um comentário

Outras pessoas também visualizaram

Conferir tópicos