Go

Autenticação JWT em APIs Go: Geração, Validação e Refresh Tokens: Do Básico ao Avançado

18 min de leitura

Autenticação JWT em APIs Go: Geração, Validação e Refresh Tokens: Do Básico ao Avançado

Entendendo JWT: Fundamentos e Estrutura JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define um método compacto e autossuficiente para transmitir informações entre partes de forma segura. Um JWT é composto por três partes separadas por pontos: header, payload e signature. Quando você vê um token como , cada seção representa um segmento diferente, todos codificados em Base64URL. A beleza do JWT está em sua natureza stateless. Diferentemente de sessões tradicionais, o servidor não precisa armazenar informações sobre o token no banco de dados. Tudo que o servidor precisa saber está contido no próprio token, assinado criptograficamente para garantir que não foi alterado. Isso torna JWTs ideais para APIs escaláveis, especialmente em arquiteturas de microsserviços onde múltiplos servidores precisam validar credenciais sem compartilhar estado. Estrutura e Componentes O header contém informações sobre o tipo de token e o algoritmo de assinatura usado. Por exemplo: . O payload (também chamado de claims) carrega os dados reais, como ID

<h2>Entendendo JWT: Fundamentos e Estrutura</h2>

<p>JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define um método compacto e autossuficiente para transmitir informações entre partes de forma segura. Um JWT é composto por três partes separadas por pontos: <strong>header</strong>, <strong>payload</strong> e <strong>signature</strong>. Quando você vê um token como <code>eyJhbGc...eyJzdWI...SflKxw...</code>, cada seção representa um segmento diferente, todos codificados em Base64URL.</p>

<p>A beleza do JWT está em sua natureza stateless. Diferentemente de sessões tradicionais, o servidor não precisa armazenar informações sobre o token no banco de dados. Tudo que o servidor precisa saber está contido no próprio token, assinado criptograficamente para garantir que não foi alterado. Isso torna JWTs ideais para APIs escaláveis, especialmente em arquiteturas de microsserviços onde múltiplos servidores precisam validar credenciais sem compartilhar estado.</p>

<h3>Estrutura e Componentes</h3>

<p>O <strong>header</strong> contém informações sobre o tipo de token e o algoritmo de assinatura usado. Por exemplo: <code>{&quot;alg&quot;:&quot;HS256&quot;,&quot;typ&quot;:&quot;JWT&quot;}</code>. O <strong>payload</strong> (também chamado de claims) carrega os dados reais, como ID do usuário, email e permissões. O <strong>signature</strong> é gerado combinando o header e payload codificados com uma chave secreta, garantindo integridade. Nenhuma dessas partes é criptografada — apenas assinada — então nunca coloque informações sensíveis como senhas no payload.</p>

<h2>Implementando JWT em Go: Geração de Tokens</h2>

<p>Para trabalhar com JWT em Go, usaremos a biblioteca <code>github.com/golang-jwt/jwt/v5</code>, que é a sucessora mantida do projeto original jwt-go. Ela oferece uma API limpa e suporta todos os algoritmos de assinatura principais. Vamos criar uma estrutura de autenticação robusta do zero.</p>

<h3>Configuração Inicial e Tipos</h3>

<p>Primeiro, definiremos os tipos e configurações que usaremos em toda a aplicação:</p>

<pre><code class="language-go">package auth

import (

&quot;errors&quot;

&quot;time&quot;

&quot;github.com/golang-jwt/jwt/v5&quot;

)

// Config mantém as configurações de JWT

type Config struct {

SecretKey string

AccessDuration time.Duration

RefreshDuration time.Duration

}

// Claims define as informações contidas no JWT

type Claims struct {

UserID int json:&quot;user_id&quot;

Email string json:&quot;email&quot;

Username string json:&quot;username&quot;

Role string json:&quot;role&quot;

jwt.RegisteredClaims

}

// TokenResponse é o retorno quando geramos um token

type TokenResponse struct {

AccessToken string json:&quot;access_token&quot;

RefreshToken string json:&quot;refresh_token&quot;

ExpiresIn int64 json:&quot;expires_in&quot;

TokenType string json:&quot;token_type&quot;

}

var ErrInvalidToken = errors.New(&quot;token inválido ou expirado&quot;)

var ErrExpiredToken = errors.New(&quot;token expirado&quot;)</code></pre>

<h3>Gerando Access Tokens</h3>

<p>Agora implementaremos a função que gera o access token. Este token é de curta duração (tipicamente 15 minutos) e contém os dados do usuário:</p>

<pre><code class="language-go">package auth

import (

&quot;fmt&quot;

&quot;time&quot;

&quot;github.com/golang-jwt/jwt/v5&quot;

)

// Manager gerencia operações com JWT

type Manager struct {

config Config

}

// NewManager cria uma nova instância do gerenciador

func NewManager(config Config) *Manager {

return &amp;Manager{config: config}

}

// GenerateAccessToken cria um novo access token

func (m *Manager) GenerateAccessToken(userID int, email, username, role string) (string, error) {

now := time.Now()

expiresAt := now.Add(m.config.AccessDuration)

claims := Claims{

UserID: userID,

Email: email,

Username: username,

Role: role,

RegisteredClaims: jwt.RegisteredClaims{

ExpiresAt: jwt.NewNumericDate(expiresAt),

IssuedAt: jwt.NewNumericDate(now),

NotBefore: jwt.NewNumericDate(now),

Issuer: &quot;api-app&quot;,

},

}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

tokenString, err := token.SignedString([]byte(m.config.SecretKey))

if err != nil {

return &quot;&quot;, fmt.Errorf(&quot;erro ao assinar token: %w&quot;, err)

}

return tokenString, nil

}</code></pre>

<p>O <code>RegisteredClaims</code> do JWT contém campos padrão como <code>ExpiresAt</code> (quando expira), <code>IssuedAt</code> (quando foi criado) e <code>Issuer</code> (quem emitiu). O algoritmo HS256 (HMAC SHA256) é simétrico — a mesma chave secreta assina e valida.</p>

<h2>Validação e Parsing de Tokens</h2>

<p>Validar um token é tão importante quanto gerá-lo. Precisamos garantir que o token não foi falsificado e que ainda está válido. Go fornece ferramentas robustas para isso através do método <code>ParseWithClaims</code>.</p>

<h3>Função de Validação Completa</h3>

<pre><code class="language-go">package auth

import (

&quot;fmt&quot;

&quot;github.com/golang-jwt/jwt/v5&quot;

)

// ValidateToken valida e extrai claims de um token

func (m Manager) ValidateToken(tokenString string) (Claims, error) {

claims := &amp;Claims{}

token, err := jwt.ParseWithClaims(

tokenString,

claims,

func(token *jwt.Token) (interface{}, error) {

// Verificar se o algoritmo é o esperado (prevenção contra &#039;none&#039; algorithm attack)

if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {

return nil, fmt.Errorf(&quot;método de assinatura inesperado: %v&quot;, token.Header[&quot;alg&quot;])

}

return []byte(m.config.SecretKey), nil

},

)

if err != nil {

return nil, fmt.Errorf(&quot;erro ao fazer parse do token: %w&quot;, err)

}

if !token.Valid {

return nil, ErrInvalidToken

}

// Verificar expiração explicitamente (ParseWithClaims já faz, mas ser explícito é bom)

if claims.ExpiresAt != nil &amp;&amp; claims.ExpiresAt.Before(time.Now()) {

return nil, ErrExpiredToken

}

return claims, nil

}</code></pre>

<p>A verificação <code>if _, ok := token.Method.(*jwt.SigningMethodHMAC)</code> é crucial para segurança. Protege contra o ataque &quot;algorithm substitution&quot; onde um atacante tenta usar <code>alg: &quot;none&quot;</code> para pular a validação. Assim garantimos que apenas HMAC é aceito.</p>

<h3>Middleware de Autenticação HTTP</h3>

<p>Em uma API real, você precisará validar tokens em cada requisição. Aqui está um middleware padrão:</p>

<pre><code class="language-go">package auth

import (

&quot;net/http&quot;

&quot;strings&quot;

)

// AuthMiddleware valida o JWT em requisições HTTP

func (m *Manager) AuthMiddleware(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Extrair o token do header Authorization

authHeader := r.Header.Get(&quot;Authorization&quot;)

if authHeader == &quot;&quot; {

http.Error(w, &quot;header Authorization ausente&quot;, http.StatusUnauthorized)

return

}

// Bearer token format: &quot;Bearer &lt;token&gt;&quot;

parts := strings.SplitN(authHeader, &quot; &quot;, 2)

if len(parts) != 2 || parts[0] != &quot;Bearer&quot; {

http.Error(w, &quot;formato de Authorization inválido&quot;, http.StatusUnauthorized)

return

}

tokenString := parts[1]

claims, err := m.ValidateToken(tokenString)

if err != nil {

http.Error(w, &quot;token inválido: &quot;+err.Error(), http.StatusUnauthorized)

return

}

// Você pode armazenar claims no contexto para uso posterior

// ctx := context.WithValue(r.Context(), &quot;claims&quot;, claims)

// next.ServeHTTP(w, r.WithContext(ctx))

next.ServeHTTP(w, r)

})

}</code></pre>

<h2>Refresh Tokens e Rotação de Credenciais</h2>

<p>Access tokens de curta duração melhoram a segurança, mas exigem que usuários façam login novamente frequentemente, prejudicando a experiência. A solução é usar <strong>refresh tokens</strong> — tokens de longa duração que só servem para obter novos access tokens. Esse padrão permite que o access token seja invalidado no servidor sem deslogar o usuário.</p>

<h3>Gerando Refresh Tokens</h3>

<p>Um refresh token é gerado junto com o access token, tem duração muito maior (dias ou semanas) e não contém dados sensíveis. Você deve armazená-lo no banco de dados para poder revogá-lo:</p>

<pre><code class="language-go">package auth

import (

&quot;crypto/rand&quot;

&quot;encoding/hex&quot;

&quot;fmt&quot;

&quot;time&quot;

)

// RefreshTokenData estrutura para armazenar refresh tokens no BD

type RefreshTokenData struct {

ID string db:&quot;id&quot;

UserID int db:&quot;user_id&quot;

Token string db:&quot;token&quot;

ExpiresAt time.Time db:&quot;expires_at&quot;

CreatedAt time.Time db:&quot;created_at&quot;

Revoked bool db:&quot;revoked&quot;

}

// GenerateTokenPair cria um access token e um refresh token

func (m Manager) GenerateTokenPair(userID int, email, username, role string) (TokenResponse, error) {

// Gerar access token

accessToken, err := m.GenerateAccessToken(userID, email, username, role)

if err != nil {

return nil, err

}

// Gerar refresh token (string aleatória segura)

refreshToken, err := generateRandomToken(32)

if err != nil {

return nil, fmt.Errorf(&quot;erro ao gerar refresh token: %w&quot;, err)

}

expiresIn := int64(m.config.AccessDuration.Seconds())

return &amp;TokenResponse{

AccessToken: accessToken,

RefreshToken: refreshToken,

ExpiresIn: expiresIn,

TokenType: &quot;Bearer&quot;,

}, nil

}

// generateRandomToken cria um token seguro aleatório

func generateRandomToken(length int) (string, error) {

b := make([]byte, length)

_, err := rand.Read(b)

if err != nil {

return &quot;&quot;, err

}

return hex.EncodeToString(b), nil

}</code></pre>

<h3>Validação e Renovação de Refresh Tokens</h3>

<p>O refresh token deve ser validado no banco de dados (verificar se foi revogado) e então um novo access token é emitido:</p>

<pre><code class="language-go">package auth

import (

&quot;database/sql&quot;

&quot;fmt&quot;

&quot;time&quot;

)

// TokenStore interface para operações com tokens no BD (você implementa)

type TokenStore interface {

SaveRefreshToken(data *RefreshTokenData) error

GetRefreshToken(token string) (*RefreshTokenData, error)

RevokeRefreshToken(token string) error

DeleteExpiredTokens() error

}

// RefreshAccessToken valida um refresh token e emite um novo access token

func (m *Manager) RefreshAccessToken(refreshToken string, store TokenStore) (string, error) {

// Buscar no banco de dados

tokenData, err := store.GetRefreshToken(refreshToken)

if err != nil {

if err == sql.ErrNoRows {

return &quot;&quot;, ErrInvalidToken

}

return &quot;&quot;, fmt.Errorf(&quot;erro ao buscar refresh token: %w&quot;, err)

}

// Verificar se foi revogado

if tokenData.Revoked {

return &quot;&quot;, fmt.Errorf(&quot;refresh token foi revogado&quot;)

}

// Verificar expiração

if time.Now().After(tokenData.ExpiresAt) {

return &quot;&quot;, ErrExpiredToken

}

// Emitir novo access token

newAccessToken, err := m.GenerateAccessToken(

tokenData.UserID,

&quot;&quot;, // você buscaria email/username do BD

&quot;&quot;,

&quot;&quot;,

)

if err != nil {

return &quot;&quot;, err

}

return newAccessToken, nil

}

// RevokeToken marca um refresh token como revogado (logout)

func (m *Manager) RevokeToken(refreshToken string, store TokenStore) error {

return store.RevokeRefreshToken(refreshToken)

}</code></pre>

<h3>Exemplo de Implementação do TokenStore com SQLite</h3>

<p>Para completar o quadro, aqui está uma implementação real com SQLite:</p>

<pre><code class="language-go">package auth

import (

&quot;database/sql&quot;

&quot;time&quot;

_ &quot;github.com/mattn/go-sqlite3&quot;

)

type SQLiteTokenStore struct {

db *sql.DB

}

func NewSQLiteTokenStore(dbPath string) (*SQLiteTokenStore, error) {

db, err := sql.Open(&quot;sqlite3&quot;, dbPath)

if err != nil {

return nil, err

}

// Criar tabela se não existir

schema := `

CREATE TABLE IF NOT EXISTS refresh_tokens (

id TEXT PRIMARY KEY,

user_id INTEGER NOT NULL,

token TEXT UNIQUE NOT NULL,

expires_at TIMESTAMP NOT NULL,

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

revoked BOOLEAN DEFAULT FALSE

);

`

if _, err := db.Exec(schema); err != nil {

return nil, err

}

return &amp;SQLiteTokenStore{db: db}, nil

}

func (s SQLiteTokenStore) SaveRefreshToken(data RefreshTokenData) error {

_, err := s.db.Exec(

`INSERT INTO refresh_tokens (id, user_id, token, expires_at, created_at, revoked)

VALUES (?, ?, ?, ?, ?, ?)`,

data.ID, data.UserID, data.Token, data.ExpiresAt, data.CreatedAt, data.Revoked,

)

return err

}

func (s SQLiteTokenStore) GetRefreshToken(token string) (RefreshTokenData, error) {

data := &amp;RefreshTokenData{}

err := s.db.QueryRow(

`SELECT id, user_id, token, expires_at, created_at, revoked

FROM refresh_tokens WHERE token = ?`,

token,

).Scan(&amp;data.ID, &amp;data.UserID, &amp;data.Token, &amp;data.ExpiresAt, &amp;data.CreatedAt, &amp;data.Revoked)

if err != nil {

return nil, err

}

return data, nil

}

func (s *SQLiteTokenStore) RevokeRefreshToken(token string) error {

_, err := s.db.Exec(

UPDATE refresh_tokens SET revoked = TRUE WHERE token = ?,

token,

)

return err

}

func (s *SQLiteTokenStore) DeleteExpiredTokens() error {

_, err := s.db.Exec(

DELETE FROM refresh_tokens WHERE expires_at &lt; ?,

time.Now(),

)

return err

}</code></pre>

<h2>Fluxo Completo: Autenticação com Login e Refresh</h2>

<p>Colocando tudo junto, aqui está um fluxo realista de autenticação em uma API HTTP:</p>

<h3>Handlers de Login e Refresh</h3>

<pre><code class="language-go">package handlers

import (

&quot;encoding/json&quot;

&quot;net/http&quot;

&quot;your-module/auth&quot;

&quot;crypto/sha256&quot;

&quot;encoding/hex&quot;

)

type LoginRequest struct {

Email string json:&quot;email&quot;

Password string json:&quot;password&quot;

}

type RefreshRequest struct {

RefreshToken string json:&quot;refresh_token&quot;

}

type AuthHandler struct {

authManager *auth.Manager

tokenStore auth.TokenStore

userStore UserStore // você implementa

}

// UserStore interface (exemplo)

type UserStore interface {

GetUserByEmail(email string) (*User, error)

}

type User struct {

ID int

Email string

Username string

Password string // hash

Role string

}

// HandleLogin autentica usuário e retorna token pair

func (h AuthHandler) HandleLogin(w http.ResponseWriter, r http.Request) {

if r.Method != http.MethodPost {

http.Error(w, &quot;método não permitido&quot;, http.StatusMethodNotAllowed)

return

}

var req LoginRequest

if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {

http.Error(w, &quot;request inválido&quot;, http.StatusBadRequest)

return

}

// Buscar usuário

user, err := h.userStore.GetUserByEmail(req.Email)

if err != nil {

http.Error(w, &quot;credenciais inválidas&quot;, http.StatusUnauthorized)

return

}

// Verificar senha (exemplo simplificado)

if !verifyPassword(req.Password, user.Password) {

http.Error(w, &quot;credenciais inválidas&quot;, http.StatusUnauthorized)

return

}

// Gerar tokens

tokenPair, err := h.authManager.GenerateTokenPair(user.ID, user.Email, user.Username, user.Role)

if err != nil {

http.Error(w, &quot;erro ao gerar tokens&quot;, http.StatusInternalServerError)

return

}

// Salvar refresh token no BD

tokenData := &amp;auth.RefreshTokenData{

ID: generateID(),

UserID: user.ID,

Token: tokenPair.RefreshToken,

ExpiresAt: time.Now().AddDate(0, 0, 7), // 7 dias

CreatedAt: time.Now(),

Revoked: false,

}

if err := h.tokenStore.SaveRefreshToken(tokenData); err != nil {

http.Error(w, &quot;erro ao salvar token&quot;, http.StatusInternalServerError)

return

}

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(tokenPair)

}

// HandleRefresh renova o access token usando refresh token

func (h AuthHandler) HandleRefresh(w http.ResponseWriter, r http.Request) {

if r.Method != http.MethodPost {

http.Error(w, &quot;método não permitido&quot;, http.StatusMethodNotAllowed)

return

}

var req RefreshRequest

if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {

http.Error(w, &quot;request inválido&quot;, http.StatusBadRequest)

return

}

// Renovar access token

newAccessToken, err := h.authManager.RefreshAccessToken(req.RefreshToken, h.tokenStore)

if err != nil {

http.Error(w, &quot;refresh token inválido: &quot;+err.Error(), http.StatusUnauthorized)

return

}

response := map[string]string{

&quot;access_token&quot;: newAccessToken,

&quot;token_type&quot;: &quot;Bearer&quot;,

}

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(response)

}

// HandleLogout revoga o refresh token

func (h AuthHandler) HandleLogout(w http.ResponseWriter, r http.Request) {

if r.Method != http.MethodPost {

http.Error(w, &quot;método não permitido&quot;, http.StatusMethodNotAllowed)

return

}

var req RefreshRequest

if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {

http.Error(w, &quot;request inválido&quot;, http.StatusBadRequest)

return

}

if err := h.authManager.RevokeToken(req.RefreshToken, h.tokenStore); err != nil {

http.Error(w, &quot;erro ao logout&quot;, http.StatusInternalServerError)

return

}

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]string{&quot;message&quot;: &quot;logout realizado&quot;})

}

// verifyPassword compara senha com hash (use bcrypt em produção!)

func verifyPassword(password, hash string) bool {

// EXEMPLO: não use SHA256 em produção! Use bcrypt.CompareHashAndPassword

h := sha256.Sum256([]byte(password))

return hex.EncodeToString(h[:]) == hash

}

func generateID() string {

// Implementar geração de ID único

return &quot;id-&quot; + time.Now().Format(&quot;20060102150405&quot;)

}</code></pre>

<h3>Configuração e Uso na Aplicação Principal</h3>

<pre><code class="language-go">package main

import (

&quot;net/http&quot;

&quot;time&quot;

&quot;your-module/auth&quot;

&quot;your-module/handlers&quot;

)

func main() {

// Configurar JWT

jwtConfig := auth.Config{

SecretKey: &quot;sua-chave-secreta-muito-segura-32-caracteres&quot;, // Use variável de ambiente!

AccessDuration: 15 * time.Minute,

RefreshDuration: 7 24 time.Hour,

}

authManager := auth.NewManager(jwtConfig)

tokenStore, _ := auth.NewSQLiteTokenStore(&quot;./tokens.db&quot;)

// Criar handler de autenticação

authHandler := &amp;handlers.AuthHandler{

authManager: authManager,

tokenStore: tokenStore,

// userStore: seu userStore implementation

}

// Rotas públicas (sem autenticação)

http.HandleFunc(&quot;/login&quot;, authHandler.HandleLogin)

http.HandleFunc(&quot;/refresh&quot;, authHandler.HandleRefresh)

http.HandleFunc(&quot;/logout&quot;, authHandler.HandleLogout)

// Rotas protegidas (com middleware)

protectedMux := http.NewServeMux()

protectedMux.HandleFunc(&quot;/profile&quot;, func(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

w.WriteHeader(http.StatusOK)

// Aqui você pode acessar as claims do contexto

w.Write([]byte({&quot;message&quot;: &quot;dados protegidos&quot;}))

})

// Aplicar middleware a rotas protegidas

http.Handle(&quot;/profile&quot;, authManager.AuthMiddleware(protectedMux))

http.ListenAndServe(&quot;:8080&quot;, nil)

}</code></pre>

<h2>Conclusão</h2>

<p>Implementar autenticação JWT em Go requer entender três pilares: a <strong>estrutura e geração de tokens</strong> (combinando header, payload e signature com uma chave secreta), a <strong>validação robusta</strong> que protege contra ataques como algorithm substitution, e o <strong>padrão refresh token</strong> que equilibra segurança com experiência do usuário. Use sempre bibliotecas mantidas como <code>golang-jwt/jwt/v5</code>, nunca implemente criptografia manualmente, e armazene refresh tokens no banco de dados para permitir revogação — uma prática crucial para logout real e segurança em compromisso de credenciais.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://pkg.go.dev/github.com/golang-jwt/jwt/v5" target="_blank" rel="noopener noreferrer">golang-jwt/jwt Documentation</a> — Documentação oficial da biblioteca JWT para Go</li>

<li><a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="noopener noreferrer">RFC 7519 - JSON Web Token (JWT)</a> — Especificação padrão de JWT</li>

<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP JWT Security Best Practices</a> — Guia de segurança JWT</li>

<li><a href="https://golang.org/doc/codewalk/functions" target="_blank" rel="noopener noreferrer">Go Security in Practice</a> — Best practices de segurança em Go</li>

<li><a href="https://go.dev/blog/" target="_blank" rel="noopener noreferrer">The Go Programming Language - Authentication Patterns</a> — Blog oficial com padrões de autenticação</li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Guia Completo de Channels Bufferizados e Direcionais em Go na Prática
Guia Completo de Channels Bufferizados e Direcionais em Go na Prática

Entendendo Channels em Go: Fundamentos Essenciais Channels são um mecanismo d...

Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo
Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo

Entendendo Interfaces em Go Interfaces em Go são um dos conceitos mais podero...

Guia Completo de Pacote time em Go: Datas, Durações, Timers e Tickers
Guia Completo de Pacote time em Go: Datas, Durações, Timers e Tickers

Introdução ao Pacote time em Go O pacote é um dos pilares fundamentais da pro...