A Gateway API atua como ponto único de entrada para todos os microserviços do domínio store. Ela é responsável por rotear, autenticar e auditar requisições externas, aplicando políticas de segurança e balanceando o tráfego entre os serviços internos.
Trusted layer e segurança
Toda requisição externa entra exclusivamente pelo Gateway. O Gateway valida o JWT, injeta o id-account nos headers e redireciona a requisição para o microserviço correspondente (account, auth, order, product, exchange etc.).
Visão geral
Service (gateway-service): Implementado em Spring Cloud Gateway (Java). Centraliza o roteamento HTTP para os demais microserviços (account, auth, order, product, exchange). Aplica as regras de segurança via filtros customizados e validação de tokens JWT.
Segurança e Autorização Toda requisição passa por AuthorizationFilter, que valida o JWT e injeta o header id-account antes do encaminhamento ao destino. O RouterValidator define quais rotas exigem autenticação. O CorsFilter habilita o compartilhamento seguro entre domínios (CORS).
classDiagram
class GatewayApplication {
+main(String[] args)
}
class GatewayResource {
+healthCheck(): String
}
class AuthorizationFilter {
+filter(exchange, chain): Mono<Void>
-isSecured(request): boolean
-validateToken(token): Claims
-injectHeaders(request, claims)
}
class RouterValidator {
+isSecured(request): boolean
}
class CorsFilter {
+filter(exchange, chain): Mono<Void>
}
GatewayApplication --> GatewayResource
GatewayResource --> AuthorizationFilter
GatewayApplication --> RouterValidator
GatewayApplication --> CorsFilter
note for AuthorizationFilter "Valida o JWT e injeta o header id-account"
note for RouterValidator "Define rotas seguras / públicas"
note for CorsFilter "Habilita o CORS para o domínio store"
Estrutura da requisição
flowchart LR
subgraph api [Trusted Layer]
direction TB
gateway e2@==> account
gateway e4@==> others
account --> db@{ shape: cyl, label: "Database" }
others --> db
end
internet e1@==>|request| gateway:::red
e1@{ animate: true }
e2@{ animate: true }
e4@{ animate: true }
classDef red fill:#fcc
packagestore.gateway;importjava.util.Map;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassGatewayResource{@GetMapping("/health-check")publicResponseEntity<Map<String,String>>healthCheck(){returnResponseEntity.ok().body(Map.of("osArch",System.getProperty("os.arch"),"osName",System.getProperty("os.name"),"osVersision",System.getProperty("os.version")));}@GetMapping("/")publicResponseEntity<String>hello(){returnResponseEntity.ok().body("API for Store");}}
packagestore.gateway.security;importjava.util.Map;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.MediaType;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.stereotype.Component;importorg.springframework.web.reactive.function.client.WebClient;importorg.springframework.web.server.ResponseStatusException;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@ComponentpublicclassAuthorizationFilterimplementsGlobalFilter{privateLoggerlogger=LoggerFactory.getLogger(AuthorizationFilter.class);privatestaticfinalStringAUTHORIZATION_HEADER="Authorization";privatestaticfinalStringAUTHORIZATION_BEARER_HEADER="Bearer";privatestaticfinalStringAUTH_SERVICE_TOKEN_SOLVE="http://auth:8080/auth/solve";@AutowiredprivateRouterValidatorrouterValidator;@AutowiredprivateWebClient.BuilderwebClient;@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){logger.debug("filter: entrou no filtro de autorizacao");ServerHttpRequestrequest=exchange.getRequest();if(!routerValidator.isSecured.test(request)){logger.debug("filter: rota nao eh segura");returnchain.filter(exchange);}logger.debug("filter: rota eh segura");if(!isAuthMissing(request)){logger.debug("filter: tem [Authorization] no Header");Stringauthorization=request.getHeaders().get(AUTHORIZATION_HEADER).get(0);logger.debug(String.format("filter: [Authorization]=[%s]",authorization));String[]parts=authorization.split(" ");if(parts.length!=2){logger.debug("filter: bearer token is invalid");thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Authorization header is not well formatted");}if(!AUTHORIZATION_BEARER_HEADER.equals(parts[0])){logger.debug("filter: bearer token is invalid");thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Authorization header is not well formatted");}logger.debug("filter: bearer token is formatted");finalStringjwt=parts[1];returnrequestAuthTokenSolve(exchange,chain,jwt);}logger.debug("filter: access is denied!");// if access is deniedthrownewResponseStatusException(HttpStatus.UNAUTHORIZED);}privatebooleanisAuthMissing(ServerHttpRequestrequest){return!request.getHeaders().containsKey(AUTHORIZATION_HEADER);}// este metodo eh responsavel por enviar o token ao Auth Microservice// a fim de interpretar o token, a chamada eh feita via Rest.privateMono<Void>requestAuthTokenSolve(ServerWebExchangeexchange,GatewayFilterChainchain,Stringjwt){logger.debug("solve: solving jwt: "+jwt);returnwebClient.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE).build().post().uri(AUTH_SERVICE_TOKEN_SOLVE).bodyValue(Map.of("jwt",jwt)).retrieve().toEntity(Map.class).flatMap(response->{if(response!=null&&response.hasBody()&&response.getBody()!=null){finalMap<String,String>map=response.getBody();StringidAccount=map.get("idAccount");logger.debug("solve: id account: "+idAccount);ServerWebExchangeauthorizated=updateRequest(exchange,idAccount);returnchain.filter(authorizated);}else{thrownewResponseStatusException(HttpStatus.UNAUTHORIZED,"Invalid token");}});}privateServerWebExchangeupdateRequest(ServerWebExchangeexchange,StringidAccount){logger.debug("original headers: "+exchange.getRequest().getHeaders().toString());ServerWebExchangemodified=exchange.mutate().request(exchange.getRequest().mutate().header("id-account",idAccount).build()).build();logger.debug("updated headers: "+modified.getRequest().getHeaders().toString());returnmodified;}}