Pular para o conteúdo
← Projetos

Backend

E-commerce API

Catálogo aberto para leitura, escrita só para ADMIN. Login com JWT, senha com BCrypt, upload de imagem e Swagger gerado do código.

9endpoints, 4 só para ADMIN

O problema

Qualquer pessoa pode olhar o catálogo, mas só quem tem conta pode mexer nele. O jeito fácil seria guardar sessão na memória do servidor, e aí a API só funciona enquanto roda numa máquina só. Eu queria resolver do jeito que aguenta crescer.

Arquitetura

Um OncePerRequestFilter valida o JWT (jjwt, HS256) e popula o SecurityContext antes de a requisição chegar ao controller. As senhas são gravadas com BCrypt. O SecurityFilterChain libera GET público e exige token na escrita. O upload é multipart com validação de tipo e proteção contra path traversal. O springdoc gera o Swagger UI a partir das anotações.

O contrato

Copiei do controller. É isto que a API responde.

POST/api/auth/login200 OK

Envio

{
  "email": "ana@exemplo.com",
  "senha": "senha-forte-aqui"
}

Resposta

{
  "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbmFAZXhlbXBsby5jb20i...",
  "tipo": "Bearer",
  "expiraEmSegundos": 3600
}

O token já vem com a validade dele na resposta.

POST/api/produtos400 Bad Request

Authorization: Bearer <token>

Envio

{
  "nome": "",
  "preco": -10
}

Resposta

{
  "type": "about:blank",
  "title": "Erro de validação",
  "status": 400,
  "detail": "Um ou mais campos são inválidos",
  "erros": {
    "nome": "nome é obrigatório",
    "preco": "preco deve ser maior que zero",
    "estoque": "estoque é obrigatório"
  }
}

Quando a validação falha, a resposta diz qual campo e por quê.

Decisões

  1. 01

    Stateless em vez de sessão

    O servidor não lembra de ninguém. Quem prova quem é, a cada chamada, é o token.

  2. 02

    Permissão num arquivo só, papel lido do banco

    Toda regra de acesso fica no SecurityFilterChain, então abro um arquivo e vejo o que está protegido. O papel vem do banco a cada requisição, não do token: custa uma consulta, mas rebaixar alguém vale na hora, em vez de esperar o token expirar.

  3. 03

    Upload tratado como entrada hostil

    Valido o tipo e limpo o nome do arquivo antes de gravar. Upload é o lugar mais fácil de alguém enfiar ../.. no caminho e sair lendo o disco.

O que ficou de pé

  • Escrita no catálogo só para ADMIN, com a regra num lugar só: o SecurityFilterChain deixa GET público e exige ADMIN em qualquer outro método. Usuário comum recebe 403 e anônimo 401, os dois no formato de erro da API
  • O registro sempre cria USER, mesmo que o JSON mande um papel. O primeiro admin de produção vem de ADMIN_EMAIL, que promove uma conta que já existe sem criar senha a partir de variável de ambiente
  • 15 testes de integração cobrindo registro, login, permissão por papel e caminhos de erro
  • Sem JWT_SECRET a API não sobe no perfil de produção, e o usuário de exemplo só existe em dev
  • A rota /error é pública: antes disso, qualquer 500 numa rota aberta chegava ao cliente como 401
  • Schema versionado com Flyway, e um teste que falha se uma entidade não tiver migration
  • Swagger UI gerado do próprio código, sem documentação paralela para envelhecer

501, o que ainda falta

  • Sem rate limit no login, sem refresh token e sem revogar token antes de ele expirar.
  • O tipo da imagem é conferido pelo Content-Type que o cliente declara, não pelos bytes do arquivo, e a imagem antiga fica no disco quando o produto troca de imagem.
  • Não existe rota para promover ou rebaixar usuário. O único caminho para ADMIN em produção é o ADMIN_EMAIL.