O módulo auth-service é a implementação executável do microsserviço Auth. Ele expõe os endpoints definidos em auth, trata a geração de JWT por meio do JwtService e delega o gerenciamento de contas ao microsserviço account via Feign. O componente RouterValidator — também apresentado aqui — é a peça que diz ao Gateway quais rotas exigem autenticação e quais são abertas a todos.
1. Rotas abertas vs. rotas protegidas
Nem todo endpoint exige autenticação. O cadastro e o login precisam ser acessíveis publicamente — caso contrário, os usuários nunca conseguiriam se autenticar. Todas as outras rotas são protegidas: elas exigem um cookie com um JWT válido.
O RouterValidator codifica essa política como um predicado simples:
flowchart LR
request[Requisição recebida]
open{É rota aberta?}
secured[Segue para o filtro]
forward[Encaminha a requisição]
request --> open
open -- Sim --> forward
open -- Não --> secured
secured --> forward
As rotas abertas definidas nesta plataforma são:
Método
Caminho
Finalidade
POST
/auth/register
Criar uma nova conta
POST
/auth/login
Autenticar-se e receber um cookie com o JWT
GET
/auth/logout
Limpar o cookie com o JWT
Todo o resto — incluindo todos os endpoints /accounts/** — exige um cookie válido.
2. O módulo auth-service
2.1 Repositório
Crie um novo repositório git para o serviço auth e adicione-o como submódulo:
Ponto de entrada do Spring Boot; habilita os clientes Feign do pacote store.account
AuthResource
Controller REST que implementa AuthController; define o cookie com o JWT no login/logout
AuthService
Lógica de negócio: delega o cadastro ao account e chama o JwtService para gerar os tokens
JwtService
Encapsula a biblioteca JJWT; assina e lê JWTs usando uma chave secreta HMAC
2.3 O JwtService em detalhe
O JwtService é responsável por duas coisas:
Gerar um token (generate): ele monta um JWT com o id da conta na claim jti, o name da conta como subject e o email como uma claim personalizada. O token é assinado com uma chave HMAC derivada do segredo codificado em base64 e expira depois da duração configurada (em milissegundos).
Validar um token (getId): ele lê o token e verifica a assinatura e, em seguida, confere se o token não é prematuro (nbf) nem está expirado (exp). Se for válido, retorna o id da conta.
sequenceDiagram
autonumber
actor User
User->>+AuthResource: POST /auth/login (LoginIn)
AuthResource->>+AuthService: login(email, password)
AuthService->>+AccountController: findByEmailAndPassword(AccountIn)
AccountController-->>-AuthService: AccountOut
AuthService->>+JwtService: generate(AccountOut, duration)
JwtService-->>-AuthService: string do JWT assinado
AuthService-->>-AuthResource: TokenOut
AuthResource->>-User: Set-Cookie: __store_jwt_token=<jwt>
server:port:8080spring:application:name:authstore:jwt:secretKey:${JWT_SECRET_KEY}duration:86400000# 24 hours in millisecondshttpOnly:${JWT_HTTP_ONLY}logging:level:root:infostore:debugorg.springframework.web:debug
packagestore.auth;importjava.util.Base64;importjava.util.Date;importjava.util.Map;importjavax.crypto.SecretKey;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.http.HttpStatus;importorg.springframework.stereotype.Service;importorg.springframework.web.server.ResponseStatusException;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtParser;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.security.Keys;importstore.account.AccountOut;@ServicepublicclassJwtService{@Value("${store.jwt.secretKey}")privateStringsecretKey;publicStringgenerate(AccountOutaccount,longduration){Datenow=newDate();returnJwts.builder().header().and().id(account.id()).issuer("Insper::PMA").claims(Map.of("email",account.email())).signWith(getKey()).subject(account.name()).notBefore(now).expiration(newDate(now.getTime()+duration)).compact();}privateSecretKeygetKey(){returnKeys.hmacShaKeyFor(Base64.getDecoder().decode(secretKey));}publicStringgetId(Stringjwt){JwtParserparser=Jwts.parser().verifyWith(getKey()).build();Claimsclaims=parser.parseSignedClaims(jwt).getPayload();Datenow=newDate();if(claims.getNotBefore().after(now)){thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Token is not valid yet!");}if(claims.getExpiration().before(now)){thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Token is expired!");}returnclaims.getId();}}
O JWT é entregue ao navegador como um cookie HttpOnly chamado __store_jwt_token. Usar um cookie HttpOnly em vez de armazenar o token no localStorage impede que o JavaScript o leia, o que mitiga ataques de XSS.
Outras flags de cookie usadas:
Flag
Valor
Finalidade
HttpOnly
true
O JavaScript não consegue acessar o cookie
Secure
true
O cookie só é enviado via HTTPS
SameSite
None
O cookie é enviado em requisições cross-site (necessário para frontends SPA)
Path
/
O cookie vale para todos os caminhos
SameSite=None exige Secure=true
Os navegadores rejeitam cookies SameSite=None sem a flag Secure. Isso significa que o frontend e a API precisam se comunicar via HTTPS em produção.
Pronto! O auth-service está pronto. Na próxima seção, vamos implementar o filtro do Gateway que intercepta cada requisição, extrai o cookie e pede ao auth-service que resolva o token.