Sistema de Autenticação de Aplicativos no Django-Ninja com Controle Granular de Permissões

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.

Tags: django-ninja Django api-authentication scope-permissions ip-whitelisting

Publicado em 7-6 03:45