Visão Geral
Este artigo apresenta um mecanismo de autenticação para APIs no Django-Ninja, inspirado em sistemas de provedores de nuvem como Tencent e Alibaba. A solução suporta múltiplos métodos de autenticação, controle de permissões baseado em escopos, restrição de IP por whitelist e verificação automática do estado do aplicativo.
Funcionalidades Principais
- Autenticação Flexível: Suporta autenticação via parâmetros de consulta, cabeçalho de requisição e Bearer Token.
- Whitelist de IP: Permite restringir acesso a endereços IP específicos ou intervalos CIDR.
- Controle de Permissões: Gerencia acessos com base em escopos (ex.:
project:read). - Verificação de Estado: Valida automaticamente se o aplicativo está ativo.
Configuração Inicial
1. Definição do Modelo de Aplicativo
Crie um modelo ApiClient para armazenar credenciais e configurações:
import uuid
from django.db import models
from django.core.validators import RegexValidator
class ApiClient(models.Model):
class Status(models.TextChoices):
ACTIVE = 'active', 'Ativo'
DISABLED = 'disabled', 'Desativado'
guid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
client_id = models.CharField(max_length=100, unique=True, validators=[
RegexValidator(regex=r'^[a-zA-Z0-9_-]+$', message='ID inválido')
])
display_name = models.CharField(max_length=200)
api_secret = models.CharField(max_length=100)
ip_whitelist = models.TextField(blank=True, null=True)
permission_scopes = models.TextField(blank=True, null=True)
status = models.CharField(max_length=10, choices=Status.choices, default=Status.ACTIVE)
def is_active(self):
return self.status == self.Status.ACTIVE
def get_scopes(self):
if not self.permission_scopes:
return []
return [s.strip() for s in self.permission_scopes.split(',') if s.strip()]
def get_allowed_ips(self):
if not self.ip_whitelist:
return []
return [ip.strip() for ip in self.ip_whitelist.split(',') if ip.strip()]
2. Implementação dos Autenticadores
Implemente classes de autenticação que integram validação de credenciais, IP e escopos:
import ipaddress
from ninja.security import APIKeyQuery, APIKeyHeader, HttpBearer
class ClientAuthMixin:
def get_real_ip(self, request):
x_forwarded = request.META.get('HTTP_X_FORWARDED_FOR')
return x_forwarded.split(',')[0].strip() if x_forwarded else request.META.get('REMOTE_ADDR')
def check_ip_access(self, client, request):
allowed = client.get_allowed_ips()
if not allowed:
return True
client_ip = self.get_real_ip(request)
for entry in allowed:
try:
if '/' in entry:
if ipaddress.ip_address(client_ip) in ipaddress.ip_network(entry, strict=False):
return True
elif client_ip == entry:
return True
except ValueError:
continue
return False
def validate_client(self, request, secret):
try:
client = ApiClient.objects.get(api_secret=secret)
if not client.is_active() or not self.check_ip_access(client, request):
return None
request.client_scopes = client.get_scopes()
return client
except ApiClient.DoesNotExist:
return None
class QueryParamAuth(ClientAuthMixin, APIKeyQuery):
param_name = 'api_key'
def authenticate(self, request, key):
return self.validate_client(request, key)
class HeaderAuth(ClientAuthMixin, APIKeyHeader):
param_name = 'X-API-Key'
def authenticate(self, request, key):
return self.validate_client(request, key)
class BearerTokenAuth(ClientAuthMixin, HttpBearer):
def authenticate(self, request, token):
return self.validate_client(request, token)
api_key_auth = QueryParamAuth()
header_auth = HeaderAuth()
bearer_auth = BearerTokenAuth()
Utilização nas APIs
Autenticação Básica
Use os autenticadores decorando as rotas:
from ninja import Router
router = Router()
@router.get('/data', auth=api_key_auth)
def fetch_data(request):
client = request.auth
return {'status': 'ok', 'client_id': client.client_id}
@router.get('/info', auth=header_auth)
def fetch_info(request):
return {'message': 'authenticated'}
@router.get('/status', auth=bearer_auth)
def check_status(request):
return {'active': True}
Controle de Permissões
Implemente verificação de escopos usando decoradores ou verificações manuais:
from core.auth import require_scopes, ScopeChecker
@router.get('/projects', auth=api_key_auth)
@require_scopes(['project:read'])
def list_projects(request):
return {'projects': []}
@router.post('/projects', auth=api_key_auth)
@require_scopes(['project:write', 'user:read'])
def create_project(request):
return {'success': True}
@router.get('/custom-check', auth=api_key_auth)
def custom_check(request):
if ScopeChecker.has_any_scope(request, ['project:read', 'project:write']):
return {'access': 'granted'}
return {'access': 'denied'}
Sistema de Escopos
Escopos seguem o formato recurso:ação e podem ser definidos como constantes:
class Scopes:
PROJECT_READ = 'project:read'
PROJECT_WRITE = 'project:write'
USER_READ = 'user:read'
ADMIN = '*'
Restrição de IP
Configure a whitelist no campo ip_whitelist com endereços individuais ou intervalos CIDR:
# Exemplos de configuração
client.ip_whitelist = '192.168.1.100,10.0.0.0/8'
Tratamento de Erros
O mecanismo retorna erros HTTP específicos:
401 Unauthorized: Falha na autenticação.403 Forbidden: Permissão negada.
Exemplo de exceção personalizada:
class PermissionError(Exception):
pass
@router.get('/resource', auth=api_key_auth)
def protected_resource(request):
if not some_condition:
raise PermissionError('Acesso negado')
return {'data': 'confidencial'}
Testes e Integração
Inclua rotas de teste para validar a autenticação:
@router.get('/test/query-auth', auth=api_key_auth)
def test_query_auth(request):
return {'authenticated': True, 'scopes': request.client_scopes}
Para integrar com rotas existenets, registre o roteador na configuração pirncipal do Django-Ninja.
Boas Práticas
- Utilize chaves fortes com pelo menos 32 caracteres.
- Rotacione segredos periodicamente.
- Armazene credenciais em variáveis de ambiente.
- Implemente logging para auditoria de acessos.
- Use whitelist de IP em ambientes de produção.