Pular para o conteúdo principal

Autenticação JWT

Este guia explica o funcionamento e a configuração da autenticação JWT no PCH-SIG.

Funcionamento

Fluxo de autenticação

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Cliente │ │ Backend │ │ PostgreSQL │
│ (Browser) │ │ (Symfony) │ │ │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ POST /api/login │ │
│ {email, password} │ │
│──────────────────▶│ │
│ │ Verificar user │
│ │──────────────────▶│
│ │◀──────────────────│
│ │ │
│ {token, refresh} │ │
│◀──────────────────│ │
│ │ │
│ GET /api/menages │ │
│ Authorization: │ │
│ Bearer <token> │ │
│──────────────────▶│ │
│ │ Verificar token │
│ │ (chave pública) │
│ {data} │ │
│◀──────────────────│ │

Tokens

TipoDuraçãoUso
Access Token1 horaAutenticar as requisições API
Refresh Token7 diasRenovar o access token

Configuração

Variáveis de ambiente

Arquivo: backend/.env

# Caminho das chaves
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem

# Passphrase para a chave privada
JWT_PASSPHRASE=sua_passphrase_segura

# Duração de validade (em segundos)
JWT_TTL=3600

Configuração LexikJWT

Arquivo: backend/config/packages/lexik_jwt_authentication.yaml

lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600

token_extractors:
authorization_header:
enabled: true
prefix: Bearer
name: Authorization

Geração das chaves

Criar as chaves JWT

# Via o comando Symfony
docker exec pch_backend php bin/console lexik:jwt:generate-keypair

# Ou manualmente com OpenSSL
openssl genrsa -out config/jwt/private.pem -aes256 4096
openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem

Verificar as chaves

# Verificar que os arquivos existem
docker exec pch_backend ls -la config/jwt/

# Testar a chave privada
docker exec pch_backend openssl rsa -in config/jwt/private.pem -check

# Verificar a chave pública
docker exec pch_backend openssl rsa -in config/jwt/public.pem -pubin -text

Permissões

# A chave privada deve ser protegida
docker exec pch_backend chmod 600 config/jwt/private.pem
docker exec pch_backend chmod 644 config/jwt/public.pem

Uso da API

Autenticação

# Obter um token
curl -X POST http://localhost:8000/api/login_check \
-H "Content-Type: application/json" \
-d '{"email":"admin@pch-sig.sn","password":"Admin123!"}'

Resposta:

{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "abc123..."
}

Usar o token

# Requisição autenticada
curl http://localhost:8000/api/menages \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."

Atualizar o token

curl -X POST http://localhost:8000/api/token/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"abc123..."}'

Estrutura do token

Payload JWT

{
"iat": 1705312345,
"exp": 1705315945,
"roles": ["ROLE_ADMIN"],
"email": "admin@pch-sig.sn",
"user_id": 1
}
CampoDescrição
iatIssued At - Timestamp de criação
expExpiration - Timestamp de expiração
rolesPapéis do usuário
emailEmail do usuário
user_idID do usuário

Decodificar um token

# Na linha de comando
echo "eyJ0eXAi..." | cut -d'.' -f2 | base64 -d | jq

# Via jwt.io (não usar em produção com tokens reais)

Personalização

Adicionar dados ao token

Arquivo: src/EventListener/JWTCreatedListener.php

namespace App\EventListener;

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;

class JWTCreatedListener
{
public function onJWTCreated(JWTCreatedEvent $event): void
{
$user = $event->getUser();
$payload = $event->getData();

// Adicionar dados personalizados
$payload['user_id'] = $user->getId();
$payload['permissions'] = $user->getPermissions();

$event->setData($payload);
}
}

Configuração do listener

# config/services.yaml
services:
App\EventListener\JWTCreatedListener:
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created }

Segurança

Boas práticas

  1. Chaves seguras

    • Usar uma passphrase forte
    • Nunca commitar as chaves no Git
    • Rotacionar as chaves regularmente
  2. Duração de vida curta

    • Access token: 1 hora máximo
    • Refresh token: 7 dias máximo
  3. HTTPS obrigatório

    • Sempre usar HTTPS em produção
    • O token transita em texto claro nos headers

Revogação de tokens

JWT não suporta nativamente a revogação. Soluções:

  1. Lista negra: Armazenar os tokens revogados no Redis
  2. Duração curta: Tokens de curta duração + refresh
  3. Versão do usuário: Invalidar todos os tokens se a versão mudar

Exemplo de lista negra

// Revogar um token
public function revokeToken(string $token): void
{
$payload = $this->jwtManager->parse($token);
$expiration = $payload['exp'] - time();

$this->redis->setex(
'blacklist:' . hash('sha256', $token),
$expiration,
'1'
);
}

// Verificar se revogado
public function isRevoked(string $token): bool
{
return $this->redis->exists('blacklist:' . hash('sha256', $token));
}

Solução de problemas

Token inválido

# Verificar a configuração
docker exec pch_backend php bin/console debug:config lexik_jwt_authentication

# Regenerar as chaves
docker exec pch_backend php bin/console lexik:jwt:generate-keypair --overwrite

# Limpar o cache
docker exec pch_backend php bin/console cache:clear

Token expirado

# Verificar a hora do servidor
docker exec pch_backend date

# Sincronizar a hora se necessário

Erro de assinatura

Causas possíveis:

  • Chaves diferentes entre ambientes
  • Passphrase incorreta
  • Arquivo de chave corrompido
# Testar a chave
docker exec pch_backend openssl rsa -in config/jwt/private.pem -check

# Se houver erro, regenerar
docker exec pch_backend php bin/console lexik:jwt:generate-keypair --overwrite

Rotação das chaves

Procedimento

  1. Gerar um novo par de chaves
  2. Configurar o sistema para aceitar as duas chaves
  3. Aguardar a expiração dos tokens antigos
  4. Remover a chave antiga

Script de rotação

# Salvar as chaves antigas
cp config/jwt/private.pem config/jwt/private.pem.old
cp config/jwt/public.pem config/jwt/public.pem.old

# Gerar as novas
php bin/console lexik:jwt:generate-keypair --overwrite

# Reiniciar a aplicação
docker restart pch_backend

Próximos passos