Especialização em Backend com Go & Gin • Gin Framework & Go 1.22+ • GORM, Clean Architecture, Concorrência e Alta Performance


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Cliente HTTP (Header: Authorization: Bearer <token>)"] --> B["Gin Engine"]
    B --> C["Middleware AutenticacaoJWT()"]
    
    subgraph PROCESSAMENTO_TOKEN["Validação Criptográfica do Token (golang-jwt/v5)"]
        C --> D{"Formato 'Bearer <token>' Válido?"}
        D -->|"Não"| E["c.AbortWithStatusJSON(401 Unauthorized)"]
        D -->|"Sim"| F["jwt.ParseWithClaims(token, &ClaimsCustomizadas{}, keyFunc)"]
        
        F --> G{"Assinatura Válida e Não Expirado?"}
        G -->|"Não"| H["c.AbortWithStatusJSON(401 Token Inválido/Expirado)"]
        G -->|"Sim"| I["c.Set('userID', claims.UserID) + c.Set('role', claims.Role)"]
    end

    I --> J["c.Next() -> Encaminha para Handler Protegido"]
    J --> K["Handler lê dados via c.Get('userID')"]
    K --> L["HTTP 200 OK + Resposta de Negócio"]

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style E fill:#ffebee,stroke:#e53935,stroke-width:2px
    style F fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style G fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style H fill:#ffebee,stroke:#e53935,stroke-width:2px
    style I fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style J fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style K fill:#e0f2f1,stroke:#009688,stroke-width:2px
    style L fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

🏛️ 1. A Arquitetura do JWT (RFC 7519) em Microsserviços

Em sistemas distribuídos de alta concorrência construídos em Go, manter sessões de autenticação na memória do servidor com cookies de sessão tradicionais impede a escalabilidade horizontal: se a requisição bater em outra réplica, a sessão não existirá, exigindo bancos de sessões compartilhados ou balanceamento com afinidade (sticky sessions).

O JSON Web Token (JWT) viabiliza o paradigma Stateless (Sem Estado): todas as informações de identidade do usuário necessárias para autorizar a requisição trafegam assinadas criptograficamente dentro do próprio token. O servidor não precisa consultar o banco de dados a cada requisição HTTP para saber quem é o usuário.

Um token JWT é composto por três partes delimitadas por ponto (.):

  1. Header (Cabeçalho): Informa o tipo de token (JWT) e o algoritmo criptográfico de assinatura (ex: HS256, RS256).
  2. Payload (Claims): Conjunto de declarações de identidade contendo claims registradas (iss, exp, sub, iat) e claims privadas customizadas (user_id, role, tenant_id).
  3. Signature (Assinatura): Hash criptográfico gerado a partir da concatenação do Header com o Payload e de uma chave secreta privada, impedindo qualquer modificação maliciosa do payload no trânsito.

Algoritmos Simétricos vs Assimétricos

Algoritmo Tipo de Chave Onde Usar Vantagens / Desvantagens
HS256 (HMAC-SHA256) Chave Simétrica Única Monólitos e APIs internas Ultra-rápido, mas todas as instâncias devem conhecer o segredo compartilhado.
RS256 / ES256 (RSA / ECDSA) Par Chave Pública / Privada Arquitetura de Microsserviços Apenas o Auth Service possui a Chave Privada para assinar; todos os outros microsserviços validam usando apenas a Chave Pública.

⚙️ 2. Implementação Canônica com golang-jwt/jwt/v5

A versão 5 da biblioteca oficial github.com/golang-jwt/jwt/v5 introduziu melhorias de segurança estritas contra o ataque de algoritmo nulo (None Algorithm Exploit):

package auth

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

var segredoJWT = []byte("chave-super-secreta-corporativa-2026")

// ClaimsCustomizadas estende as claims padrão da RFC 7519
type ClaimsCustomizadas struct {
	UserID uint   `json:"user_id"`
	Email  string `json:"email"`
	Role   string `json:"role"`
	jwt.RegisteredClaims
}

// GerarToken emite um novo JWT assinado com validade de 2 horas
func GerarToken(userID uint, email, role string) (string, error) {
	claims := ClaimsCustomizadas{
		UserID: userID,
		Email:  email,
		Role:   role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "api-corporativa-gin",
			Subject:   email,
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(segredoJWT)
}

// ValidarToken inspeciona a assinatura e desempacota as claims
func ValidarToken(tokenString string) (*ClaimsCustomizadas, error) {
	token, err := jwt.ParseWithClaims(tokenString, &ClaimsCustomizadas{}, func(t *jwt.Token) (interface{}, error) {
		// Proteção estrita contra adulteração de algoritmo no header
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("método de assinatura inesperado")
		}
		return segredoJWT, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*ClaimsCustomizadas); ok && token.Valid {
		return claims, nil
	}

	return nil, errors.New("token inválido ou com claims corrompidas")
}

🛡️ 3. Middleware de Autenticação no Gin

O middleware intercepta o header HTTP, valida o formato Bearer e popula o contexto (c.Set) para consumo imediato nos endpoints seguintes:

package middleware

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"meu-modulo/auth"
)

func AutenticacaoJWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		cabecalhoAuth := c.GetHeader("Authorization")
		if cabecalhoAuth == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"erro": "cabeçalho de autorização não fornecido",
			})
			return
		}

		partes := strings.Split(cabecalhoAuth, " ")
		if len(partes) != 2 || strings.ToLower(partes[0]) != "bearer" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"erro": "formato de cabeçalho inválido. Utilize 'Bearer <token>'",
			})
			return
		}

		claims, err := auth.ValidarToken(partes[1])
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"erro":     "token de acesso inválido ou expirado",
				"detalhes": err.Error(),
			})
			return
		}

		// Injeta as informações do usuário autenticado no contexto da requisição
		c.Set("userID", claims.UserID)
		c.Set("userEmail", claims.Email)
		c.Set("userRole", claims.Role)

		c.Next()
	}
}

🔒 4. Padrão Dual-Token: Access Token e Refresh Token

Em conformidade com as diretrizes do OWASP, tokens JWT não devem ter tempo de expiração prolongado (ex: dias ou semanas), pois se forem interceptados não há mecanismo nativo de revogação imediata.

A arquitetura canônica adota o Padrão Dual-Token:

  • Access Token: Tempo de vida curto (15 a 30 minutos). Utilizado diretamente nas chamadas da API.
  • Refresh Token: Tempo de vida longo (7 a 30 dias). Armazenado com segurança (HttpOnly Cookie) e persistido no Redis. Quando o Access Token expira, o cliente solicita um novo par de tokens sem exigir nova digitação de senha pelo usuário.