Skip to content

Auth API

A Auth API é responsável pela autenticação de usuários e geração de tokens JWT utilizados por todos os demais microserviços do domínio store.
Ela valida credenciais, emite tokens de acesso e garante a integridade das comunicações dentro da arquitetura de microsserviços.

Trusted layer e segurança

O acesso à Auth API é realizado via Gateway.
Após o login, o JWT é retornado ao cliente e utilizado nas demais requisições aos serviços protegidos (account, order, product, exchange).


Visão geral

  • Service (auth-service): Implementado em Spring Boot (Java).
    Expõe endpoints públicos para autenticação e registro de usuários.
    Integra-se com o account-service para validação de usuários e gera tokens JWT assinados com a chave definida em JWT_SECRET_KEY.

  • Fluxo de autenticação

  • O cliente envia email e password para /auth/login.
  • O Auth API valida as credenciais no account-service.
  • Em caso de sucesso, é gerado e retornado o token JWT.
  • O gateway-service utiliza esse token para validar requisições futuras e injetar o id-account nos headers.
classDiagram
    namespace auth {
        class AuthController {
            +register(RegisterIn RegisterIn): TokenOut
            +login(LoginIn loginIn): TokenOut
        }
        class RegisterIn {
            -String name
            -String email
            -String password
        }
        class LoginIn {
            -String name
            -String email
        }
        class TokenOut {
            -String token
        }
        class SolveOut {
            -String idAccount
        }
    }
    namespace auth-service {
        class AuthResource {
            +register(RegisterIn RegisterIn) TokenOut
            +login(LoginIn loginIn) TokenOut
        }
        class AuthService {
            +register(Register) Regiter
            +login(LoginIn loginIn) String
        }
        class Register {
            -String id
            -String name
            -String email
            -String password
        }
    }
    <<Interface>> AuthController
    AuthController ..> RegisterIn
    AuthController ..> LoginIn
    AuthController ..> TokenOut

    AuthController <|-- AuthResource
    AuthResource *-- AuthService
    AuthService ..> Register

Estrutura da requisição

flowchart LR
    subgraph api [Trusted Layer]
        direction TB
        gateway --> account
        gateway --> others
        gateway e4@==> auth:::red
        auth e2@==> account
        account --> db@{ shape: cyl, label: "Database" }
        others --> db
    end
    internet e1@==>|request| gateway:::orange
    e1@{ animate: true }
    e2@{ animate: true }
    e4@{ animate: true }
    classDef red fill:#fcc
    classDef orange fill:#FCBE3E

Auth

📁 api/
└── 📁 auth/
    ├── 📁 src/
       └── 📁 main/
           └── 📁 java/
               └── 📁 store/
                   └── 📁 auth/
                       ├── 📄 AuthController.java
                       ├── 📄 LoginIn.java
                       ├── 📄 RegisterIn.java
                       └── 📄 TokenOut.java
    ├── 📄 pom.xml
    └── 📄 Jenkinsfile
Source
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/>
    </parent>

    <groupId>store</groupId>
    <artifactId>auth</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>21</java.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
        <maven.compiler.proc>full</maven.compiler.proc>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                sh 'mvn -B -DskipTests clean install'
            }
        }
    }

}
package store.auth;

import java.util.Map;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@FeignClient(name = "auth", url = "http://auth:8080")
public interface AuthController {

    @PostMapping("/auth/register")
    public ResponseEntity<TokenOut> register(
        @RequestBody RegisterIn in
    );

    @PostMapping("/auth/login")
    public ResponseEntity<TokenOut> login(
        @RequestBody LoginIn in
    );

    @PostMapping("/auth/solve")
    public ResponseEntity<Map<String, String>> solve(
        @RequestBody TokenOut in
    );

}
package store.auth;

import lombok.Builder;

@Builder
public record LoginIn(
    String email,
    String password
) {

}
package store.auth;

import lombok.Builder;

@Builder
public record RegisterIn(
    String name,
    String email,
    String password
) {

}
package store.auth;

import lombok.Builder;

@Builder
public record TokenOut (
    String jwt
) {

}
mvn clean install

Auth-Service

📁 api/
└── 📁 auth-service/
    ├── 📁 k8s/
       └── 📄 k8s.yaml
    ├── 📁 src/
       ├── 📁 main/
          └── 📁 java/
              └── 📁 store/
                  └── 📁 auth/
                      ├── 📄 AuthApplication.java
                      ├── 📄 AuthResource.java
                      ├── 📄 AuthService.java
                      └── 📄 JwtService.java
       └── 📁 resources/
           └── 📄 application.yaml
    ├── 📄 pom.xml
    ├── 📄 Dockerfile
    └── 📄 Jenkinsfile
Source
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/>
    </parent>

    <groupId>store</groupId>
    <artifactId>auth-service</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>21</java.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
        <maven.compiler.proc>full</maven.compiler.proc>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>auth</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>account</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>[0.13,)</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>[0.13,)</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
            <version>[0.13,)</version>
            <scope>runtime</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
1
2
3
4
FROM openjdk:23-slim
VOLUME /tmp
COPY target/*.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
pipeline {
    agent any
    environment {
        SERVICE = 'auth'
        NAME = "jpqv/${env.SERVICE}"
        AWS_REGION  = "us-east-2"
        EKS_CLUSTER = "eks-store"
    }
    stages {
        stage('Dependecies') {
            steps {
                build job: 'auth', wait: true
            }
        }
        stage('Build') { 
            steps {
                sh 'mvn -B -DskipTests clean package'
            }
        }      
        stage('Build & Push Image') {
            steps {
                withCredentials([usernamePassword(
                    credentialsId: 'dockerhub-credential',
                    usernameVariable: 'USERNAME',
                    passwordVariable: 'TOKEN')])
                {
                    sh "docker login -u $USERNAME -p $TOKEN"
                    sh "docker buildx create --use --platform=linux/arm64,linux/amd64 --node multi-platform-builder-${env.SERVICE} --name multi-platform-builder-${env.SERVICE}"
                    sh "docker buildx build --platform=linux/arm64,linux/amd64 --push --tag ${env.NAME}:latest --tag ${env.NAME}:${env.BUILD_ID} -f DockerFile ."
                    sh "docker buildx rm --force multi-platform-builder-${env.SERVICE}"
                }
            }
        }
        stage('Deploy to EKS') {
                steps {
                    withCredentials([[$class: 'AmazonWebServicesCredentialsBinding',
                    credentialsId: 'aws-credential',
                    accessKeyVariable: 'AWS_ACCESS_KEY_ID',
                    secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) 
                {
                    sh """
                        # garante diretório padrão do kubeconfig
                        mkdir -p ~/.kube

                        # configura contexto do cluster no caminho padrão (~/.kube/config)
                        aws eks update-kubeconfig --region ${AWS_REGION} --name ${EKS_CLUSTER}

                        kubectl config current-context

                        # aplica manifest inicial se ainda não existir
                        if ! kubectl get deploy ${SERVICE} >/dev/null 2>&1; then
                        kubectl apply -f ./k8s/k8s.yaml
                        fi

                        # atualiza a imagem do Deployment
                        kubectl set image deploy/${SERVICE} ${SERVICE}=${NAME}:${BUILD_ID} --record

                        # espera o rollout
                        kubectl rollout status deployment/${SERVICE} --timeout=180s
                    """
                }
            }
        }
    }
}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth
spec:
  replicas: 1
  selector:
    matchLabels:
      app: auth
  template:
    metadata:
      labels:
        app: auth
    spec:
      containers:
        - name: auth
          image: jpqv/auth:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          env:
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: postgres-configmap
                  key: POSTGRES_DB
            - name: DATABASE_USERNAME
              valueFrom:
                secretKeyRef:
                  name: postgres-secrets
                  key: POSTGRES_USER
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secrets
                  key: POSTGRES_PASSWORD
            - name: DATABASE_URL
              value: "jdbc:postgresql://postgres:5432/$(POSTGRES_DB)"
          resources:
            requests:
              memory: "200Mi"
              cpu: "50m"
            limits:
              memory: "300Mi"
              cpu: "200m"

---

apiVersion: v1
kind: Service
metadata:
  name: auth
  labels:
    app: auth
spec:
  type: ClusterIP
  ports:
    - port: 80
      protocol: TCP
      targetPort: 8080

  selector:
    app: auth
server:
  port: 8080

spring:
  application:
    name: auth

  mvc:
    problemdetails:
      enabled: true

store:
  jwt:
    secretKey: ${JWT_SECRET_KEY:changeit}

logging:
  level:
    root: info
    store: debug
package store.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients(basePackages = {
    "store.account"
})
@SpringBootApplication
public class AuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }

}
package store.auth;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import store.account.AccountOut;

@RestController
public class AuthResource implements AuthController {

    @Autowired
    private AuthService authService;

    @Override
    public ResponseEntity<TokenOut> register(RegisterIn in) {
        final String jwt = authService.register(
            in.name(), in.email(), in.password()
        );
        return ResponseEntity
            .created(
                ServletUriComponentsBuilder.fromCurrentRequest().build().toUri()
            )
            .body(TokenOut.builder().jwt(jwt).build());
    }

    @Override
    public ResponseEntity<TokenOut> login(LoginIn in) {
        final String jwt = authService.login(
            in.email(),
            in.password()
        );
        return ResponseEntity
            .created(
                ServletUriComponentsBuilder.fromCurrentRequest().build().toUri()
            )
            .body(TokenOut.builder().jwt(jwt).build());
    }

    @Override
    public ResponseEntity<Map<String, String>> solve(TokenOut in) {
        AccountOut account = authService.solve(in.jwt());
        return ResponseEntity.ok(
            Map.of(
                "idAccount", account.id()
            )
        );
    }

}
package store.auth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import store.account.AccountController;
import store.account.AccountIn;
import store.account.AccountOut;

@Service
public class AuthService {

    private Logger logger = LoggerFactory.getLogger(AuthService.class);

    @Autowired
    private AccountController accountController;

    @Autowired
    private JwtService jwtService;

    public String register(String name, String email, String password) {

        logger.debug(
            String.format(
                "registrando uma conta: [%s] for [%s]",
                name, email
            )
        );

        // Salvar no servico de Account
        AccountOut account = accountController.create(AccountIn.builder()
            .name(name)
            .email(email)
            .password(password)
            .build()
        ).getBody();

        // Gera um token
        String jwtString = jwtService.generate(account);

        // Retorna o token
        return jwtString;
    }

    public String login(String email, String password) {

        logger.debug(String.format("required login for %s:%s", email, password));

        // Verify credentials
        ResponseEntity<AccountOut> response = accountController.findByEmailAndPassword(
            AccountIn.builder()
                .email(email)
                .password(password)
                .build()
        );
        if (!response.hasBody()) {
            logger.debug(String.format("user not found"));
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
        }

        AccountOut account = response.getBody();
        logger.debug(String.format("found user", account));

        // generate token
        return jwtService.generate(account);
    }

    public AccountOut solve(String jwt) {
        return AccountOut.builder()
            .id(jwtService.getId(jwt))
            .build();
    }

}
package store.auth;

import java.util.Date;
import java.util.Map;

import javax.crypto.SecretKey;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import store.account.AccountOut;

@Service
public class JwtService {

    @Value("${store.jwt.secretKey}")
    private String secretKey;

    public String generate(AccountOut account) {

        Date now = new Date();

        String jwt = Jwts.builder()
            .header()
            .and()
            .id(account.id())
            .issuer("Insper::PMA")
            .claims(Map.of(
                "email", account.email()
            ))
            .signWith(getKey())
            .subject(account.name())
            .notBefore(now)
            .expiration(new Date(now.getTime() + 1000 * 60 * 120)) // em milisegundos
            .compact();
        return jwt;

    }

    public String getId(String jwt) {
        // constroe o parser
        JwtParser parser = Jwts.parser().verifyWith(getKey()).build();
        // recupero os atributos
        Claims claims = parser.parseSignedClaims(jwt).getPayload();
        Date now = new Date();
        if (claims.getNotBefore().after(now)) {
            throw new ResponseStatusException(
                HttpStatus.UNAUTHORIZED,
                "Token is not valid yet!"
            );
        }
        if (claims.getExpiration().before(now)) {
            throw new ResponseStatusException(
                HttpStatus.UNAUTHORIZED,
                "Token is expired!"
            );
        }
        return claims.getId();
    }

    private SecretKey getKey() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
    }

}
mvn clean package spring-boot:run