d. Autorização
O gateway-service é o ponto único de entrada de todas as requisições. Além do roteamento, ele garante a autenticação: antes de encaminhar qualquer requisição a um serviço protegido, verifica se há um cookie JWT válido e resolve a identidade de quem fez a chamada. Isso é feito por dois componentes — RouterValidator e AuthorizationFilter.
1. Como a autorização funciona
O Gateway usa o mecanismo de GlobalFilter do Spring Cloud Gateway. Toda requisição passa pelo AuthorizationFilter antes de ser roteada. O filtro segue esta árvore de decisão:
flowchart TD
A[Requisição recebida] --> B{RouterValidator\nisSecured?}
B -- Não --> C[Encaminha ao serviço]
B -- Sim --> D{Cookie\n__store_jwt_token\npresente?}
D -- Não --> E[401 Unauthorized]
D -- Sim --> F[POST /auth/solve\ncom o JWT]
F --> G{Token válido?}
G -- Não --> E
G -- Sim --> H[Adiciona o header id-account]
H --> C Quando o token é válido, o filtro enriquece a requisição repassada com um header:
| Header | Valor | Finalidade |
|---|---|---|
id-account | UUID da conta, extraído do token (jti) | Os serviços o usam para identificar quem chamou |
Antes disso, o Gateway remove qualquer id-account enviado pelo cliente (RemoveRequestHeader=id-account em default-filters, que rodam antes do AuthorizationFilter): senão qualquer pessoa poderia chamar uma rota protegida se passando por outra conta. O header que chega aos serviços só pode vir do Gateway.
Isso significa que os microsserviços seguintes nunca precisam interpretar JWTs por conta própria — o Gateway cuida disso e repassa a identidade já resolvida em um header simples.
2. Sequência completa da requisição
sequenceDiagram
autonumber
actor User as Usuário
User->>+Gateway: requisição com o cookie __store_jwt_token
Gateway->>+AuthorizationFilter: filter(exchange, chain)
AuthorizationFilter->>RouterValidator: isSecured.test(request)
RouterValidator-->>AuthorizationFilter: true
AuthorizationFilter->>AuthorizationFilter: lê o valor do cookie
AuthorizationFilter->>+Auth Service: POST /auth/solve (token)
Auth Service->>Auth Service: JwtService.getId(token)
Auth Service-->>-AuthorizationFilter: { idAccount: "..." }
AuthorizationFilter->>AuthorizationFilter: altera os headers da requisição
AuthorizationFilter->>-Gateway: chain.filter(exchange alterado)
Gateway->>-User: resposta do serviço 3. O módulo gateway-service
3.1 Repositório
Crie um novo repositório git para o serviço de gateway e adicione-o como submódulo:
3.2 Código
Use o Spring Initializr para gerar um projeto Maven com as seguintes configurações:
- Group:
store - Artifact:
gateway-service - Package name:
store.gateway - Packaging: Jar
- Java: 25
Dependências a adicionar:
- Gateway (
spring-cloud-starter-gateway-server-webflux) — gateway reativo com suporte a roteamento e filtros - Reactive Web (
spring-boot-starter-webflux) — stack reativa exigida pelo Spring Cloud Gateway
Stack reativa
O Spring Cloud Gateway roda sobre o Project Reactor (WebFlux), e não sobre a stack tradicional de servlets. É por isso que o AuthorizationFilter devolve Mono<Void> em vez de void, e que o cliente HTTP dentro dele usa WebClient em vez de RestTemplate.
A estrutura resultante é:
📁 api/
└── 📁 gateway-service/
├── 📁 src/
│ └── 📁 main/
│ ├── 📁 java/
│ │ └── 📁 store/
│ │ └── 📁 gateway/
│ │ ├── GatewayServiceApplication.java
│ │ ├── GatewayResource.java
│ │ └── 📁 security/
│ │ ├── AuthorizationFilter.java
│ │ └── RouterValidator.java
│ └── 📁 resources/
│ └── application.yaml
├── pom.xml
└── Dockerfile
| Classe | Descrição |
|---|---|
GatewayServiceApplication | Ponto de entrada do Spring Boot |
GatewayResource | O GET /health-check do próprio Gateway; responde com o HOSTNAME do container, útil para ver qual réplica respondeu |
RouterValidator | Guarda a lista de rotas abertas; oferece o predicado isSecured |
AuthorizationFilter | Filtro global; valida o cookie JWT e enriquece a requisição |
3.3 O RouterValidator em detalhe
O RouterValidator define quais rotas dispensam autenticação. Ele as guarda como strings "METHOD /path" e expõe o predicado isSecured. Uma requisição corresponde a uma rota aberta se o método HTTP e o caminho coincidem (com suporte opcional ao curinga /**).
private List<String> openApiEndpoints = List.of(
"POST /auth/register",
"GET /auth/logout",
"POST /auth/login"
);
Qualquer requisição que não esteja nesta lista faz isSecured devolver true, ou seja, o filtro vai exigir um cookie válido.
3.4 Configuração da aplicação
O application.yaml define a política de CORS, os filtros padrão e a tabela de rotas. As rotas associam padrões de URL aos hostnames dos serviços de destino (resolvidos dentro da rede do Docker Compose):
default-filters:
- RemoveRequestHeader=id-account
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
routes:
- id: account-service
uri: http://account:8080
predicates:
- Path=/accounts/**
- id: auth-service
uri: http://auth:8080
predicates:
- Path=/auth/**
Código-fonte
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
Health check: exponha só o necessário
O /health-check é um endpoint do próprio Gateway, não uma rota, então o AuthorizationFilter não roda nele — qualquer pessoa pode chamá-lo. Responda só o que ele precisa (aqui, o hostname); devolver System.getenv() exporia todas as variáveis de ambiente, segredos inclusive.
Token inválido → 401
O retrieve() transforma uma resposta de erro do /auth/solve em exceção, e uma exceção não tratada no Gateway vira 500. O onStatus(...) converte o 401 de um token inválido ou expirado em 401 no Gateway, para o front end saber que precisa fazer login de novo.
4. Atualizando o Docker Compose
Adicione o serviço auth ao compose.yaml do Hands-on 2. Como o account, ele não publica porta: só o Gateway é acessível de fora, enquanto account e auth são alcançados apenas dentro da rede do Compose, pelos nomes dos serviços — a camada confiável.
auth e gateway não precisam de variáveis para rodar na sua máquina: o application.yaml do auth lê JWT_SECRET_KEY, JWT_HTTP_ONLY e JWT_DURATION com valores padrão de desenvolvimento, e o Gateway lê CORS_ALLOWED_ORIGINS (padrão http://localhost,http://localhost:3000,http://localhost:8080).
JWT_SECRET_KEY fora do desenvolvimento
A chave padrão do application.yaml é pública — está no repositório —, então qualquer pessoa conseguiria forjar tokens para uma implantação que a mantenha. Gere uma chave sua, com 256 bits (32 bytes aleatórios, codificados em Base64):
e entregue-a ao auth pelo ambiente — por exemplo, no compose.yaml, com o valor em um arquivo .env que nunca é commitado:
5. Testando a camada de segurança
Suba a plataforma:
Cadastre um novo usuário:
curl -X POST http://localhost:8080/auth/register \
-H "Content-Type: application/json" \
-d '{"name": "Antonio do Estudo", "email": "acme@insper.edu.br", "password": "123@321"}'
Faça login e capture o cookie:
curl -c cookies.txt -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "acme@insper.edu.br", "password": "123@321"}'
Acesse um endpoint protegido usando o cookie:
Acesse sem o cookie — espere um 401:
Pronto! A camada de segurança está completa. Toda requisição a uma rota protegida agora passa pelo Gateway, que valida o cookie JWT com o auth-service e repassa a identidade resolvida aos serviços seguintes — sem que nenhum serviço precise interpretar tokens por conta própria.