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
| Tipo | Duração | Uso |
|---|---|---|
| Access Token | 1 hora | Autenticar as requisições API |
| Refresh Token | 7 dias | Renovar 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
}
| Campo | Descrição |
|---|---|
iat | Issued At - Timestamp de criação |
exp | Expiration - Timestamp de expiração |
roles | Papéis do usuário |
email | Email do usuário |
user_id | ID 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
-
Chaves seguras
- Usar uma passphrase forte
- Nunca commitar as chaves no Git
- Rotacionar as chaves regularmente
-
Duração de vida curta
- Access token: 1 hora máximo
- Refresh token: 7 dias máximo
-
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:
- Lista negra: Armazenar os tokens revogados no Redis
- Duração curta: Tokens de curta duração + refresh
- 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
- Gerar um novo par de chaves
- Configurar o sistema para aceitar as duas chaves
- Aguardar a expiração dos tokens antigos
- 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