Saltar a contenido

Autenticacion y Autorizacion con JWT

Para implementar autenticación y autorización con JWT Token paso a paso:

Con ViewSet

pip install djangorestframework-simplejwt

Actualizar requirements.txt

pip freeze > requirements.txt

Configuración en settings.py

Archivo sitealmacen/sitealmacen/settings.py

from pathlib import Path
from decouple import config
from datetime import timedelta
...
...
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapps.sale.apps.SaleConfig',
    'myapps.product.apps.ProductConfig',
    'myapps.client.apps.ClientConfig',
    'rest_framework',
    'rest_framework_simplejwt',
    # CORS
    'corsheaders',
]

....
....
....



....
....
....

# REST Framework Configuration
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20
}

# Simple JWT Configuration
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=60),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

# CORS Configuration for JWT
CORS_ALLOW_ALL_ORIGINS = True  # Solo para desarrollo
CORS_ALLOW_CREDENTIALS = True

Crear grupos y permisos

Crear archivo sitealmacen/setup_groups.py:

import os
import django

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sitealmacen.settings')
django.setup()

from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from myapps.client.models import Client
from myapps.product.models import Product, ProductType
from myapps.sale.models import Sale, ProductSale

def create_groups_and_permissions():
    print("🔧 Configurando grupos y permisos...")

    # ==================== CREAR GRUPOS ====================
    admin_group, created = Group.objects.get_or_create(name='Administradores')
    if created:
        print("✓ Grupo 'Administradores' creado")

    manager_group, created = Group.objects.get_or_create(name='Gerentes')
    if created:
        print("✓ Grupo 'Gerentes' creado")

    employee_group, created = Group.objects.get_or_create(name='Empleados')
    if created:
        print("✓ Grupo 'Empleados' creado")

    client_group, created = Group.objects.get_or_create(name='Clientes')
    if created:
        print("✓ Grupo 'Clientes' creado")

    # ==================== OBTENER CONTENT TYPES ====================
    client_ct = ContentType.objects.get_for_model(Client)
    product_ct = ContentType.objects.get_for_model(Product)
    product_type_ct = ContentType.objects.get_for_model(ProductType)
    sale_ct = ContentType.objects.get_for_model(Sale)
    product_sale_ct = ContentType.objects.get_for_model(ProductSale)

    print("\n📋 Configurando permisos por grupo...")

    # ==================== ADMINISTRADORES: TODOS LOS PERMISOS ====================
    admin_permissions = Permission.objects.filter(
        content_type__in=[client_ct, product_ct, product_type_ct, sale_ct, product_sale_ct]
    )
    admin_group.permissions.set(admin_permissions)
    print(f"✓ Administradores: {admin_permissions.count()} permisos (CRUD completo)")

    # ==================== GERENTES: CRUD EXCEPTO ELIMINAR ====================
    # Pueden crear, leer y actualizar, pero NO eliminar
    manager_permissions = Permission.objects.filter(
        content_type__in=[client_ct, product_ct, product_type_ct, sale_ct, product_sale_ct]
    ).exclude(codename__startswith='delete_')
    manager_group.permissions.set(manager_permissions)
    print(f"✓ Gerentes: {manager_permissions.count()} permisos (CR[U]D sin Delete)")

    # ==================== EMPLEADOS: PERMISOS ESPECÍFICOS ====================
    employee_permissions = []

    # CLIENTES: Solo ver
    employee_permissions.extend(Permission.objects.filter(
        content_type=client_ct,
        codename__in=['view_client']
    ))

    # PRODUCTOS: Ver y actualizar stock
    employee_permissions.extend(Permission.objects.filter(
        content_type=product_ct,
        codename__in=['view_product', 'change_product']
    ))

    # TIPOS DE PRODUCTO: Solo ver
    employee_permissions.extend(Permission.objects.filter(
        content_type=product_type_ct,
        codename__in=['view_producttype']
    ))

    # VENTAS: CRUD completo (su función principal)
    employee_permissions.extend(Permission.objects.filter(
        content_type=sale_ct,
        codename__in=['view_sale', 'add_sale', 'change_sale', 'delete_sale']
    ))

    # PRODUCTOS-VENTAS: CRUD completo
    employee_permissions.extend(Permission.objects.filter(
        content_type=product_sale_ct,
        codename__in=['view_productsale', 'add_productsale', 'change_productsale', 'delete_productsale']
    ))

    employee_group.permissions.set(employee_permissions)
    print(f"✓ Empleados: {len(employee_permissions)} permisos (Ventas + consultas)")

    # ==================== CLIENTES: SOLO VER SUS PROPIOS DATOS ====================
    client_permissions = Permission.objects.filter(
        content_type=client_ct,
        codename__in=['view_client', 'change_client']
    )
    # Pueden ver productos para hacer compras
    client_permissions = list(client_permissions) + list(Permission.objects.filter(
        content_type=product_ct,
        codename__in=['view_product']
    ))
    # Pueden ver tipos de productos
    client_permissions.extend(Permission.objects.filter(
        content_type=product_type_ct,
        codename__in=['view_producttype']
    ))
    # Pueden ver sus propias ventas
    client_permissions.extend(Permission.objects.filter(
        content_type=sale_ct,
        codename__in=['view_sale']
    ))

    client_group.permissions.set(client_permissions)
    print(f"✓ Clientes: {len(client_permissions)} permisos (Solo consultas)")

    print("\n🎉 ¡Configuración de permisos completada!")

    # ==================== MOSTRAR RESUMEN ====================
    print("\n📊 RESUMEN DE PERMISOS POR GRUPO:")
    print("=" * 50)

    groups_info = [
        ('Administradores', admin_group, '🔴 CRUD completo en todos los modelos'),
        ('Gerentes', manager_group, '🟠 CRU (sin Delete) en todos los modelos'),
        ('Empleados', employee_group, '🟡 CRUD en Ventas, R/U en Productos, R en Clientes'),
        ('Clientes', client_group, '🟢 Solo consultas (R) y actualizar perfil propio')
    ]

    for group_name, group_obj, description in groups_info:
        perms = group_obj.permissions.all()
        print(f"\n{group_name}:")
        print(f"  {description}")
        print(f"  Total permisos: {perms.count()}")

        # Agrupar permisos por modelo
        perm_by_model = {}
        for perm in perms:
            model_name = perm.content_type.model
            if model_name not in perm_by_model:
                perm_by_model[model_name] = []
            action = perm.codename.split('_')[0]
            perm_by_model[model_name].append(action)

        for model, actions in perm_by_model.items():
            print(f"    {model}: {', '.join(sorted(actions))}")

def create_test_users():
    print("\n👥 Creando usuarios de prueba...")

    users_data = [
        ('admin', 'admin@test.com', 'admin123', 'Administradores', True, True),
        ('manager', 'manager@test.com', 'manager123', 'Gerentes', True, False),
        ('employee', 'employee@test.com', 'employee123', 'Empleados', False, False),
        ('client', 'client@test.com', 'client123', 'Clientes', False, False),
    ]

    for username, email, password, group_name, is_staff, is_superuser in users_data:
        if not User.objects.filter(username=username).exists():
            user = User.objects.create_user(username, email, password)
            user.is_staff = is_staff
            user.is_superuser = is_superuser
            user.save()

            group = Group.objects.get(name=group_name)
            user.groups.add(group)

            status = "superuser" if is_superuser else "staff" if is_staff else "user"
            print(f"✓ Usuario '{username}' creado ({group_name} - {status})")
        else:
            print(f"⚠️  Usuario '{username}' ya existe")

if __name__ == '__main__':
    create_groups_and_permissions()
    create_test_users()
    print("\n🚀 ¡Configuración completa! Ahora puedes usar autenticación JWT con permisos granulares.")

En el terminal:

python3 setup_groups.py

URLs para autenticación

archivo sitealmacen/sitealmacen/urls.py

from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
    TokenBlacklistView,  # Agregar esta línea
)

urlpatterns = [
    path('admin/', admin.site.urls),

    # Autenticación JWT
    path('api/auth/login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('api/auth/verify/', TokenVerifyView.as_view(), name='token_verify'),
    path('api/auth/logout/', TokenBlacklistView.as_view(), name='token_blacklist'),  # Agregar esta línea

    # Para ViewSet:
    path('api/', include('myapps.client.urls_viewset')),
]

IMPLEMENTACIÓN 1: ViewSet

Actualizar ViewSet con permisos

archivo myapps/client/views_viewset.py

from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.contrib.auth.models import Group
from django.utils import timezone
from .models import Client
from .serializers import ClientSerializer
from myapps.utils.permissions import (
    IsAdminUser, IsManagerOrAdmin, IsEmployeeOrAbove, ModelPermissions
)

class ClientViewSet(viewsets.ModelViewSet):
    queryset = Client.objects.all()
    serializer_class = ClientSerializer
    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    def get_permissions(self):
        """Asignar permisos dinámicos según la acción"""
        if self.action == 'list':
            # Cambiar a una verificación más simple para listar
            permission_classes = [permissions.IsAuthenticated]
        elif self.action == 'create':
            # Crear: Solo administradores y gerentes
            permission_classes = [IsManagerOrAdmin]
        elif self.action in ['update', 'partial_update']:
            # Actualizar: Solo administradores y gerentes
            permission_classes = [IsManagerOrAdmin]
        elif self.action == 'destroy':
            # Eliminar: Solo administradores
            permission_classes = [IsAdminUser]
        elif self.action == 'retrieve':
            # Ver detalle: Todos los autenticados
            permission_classes = [permissions.IsAuthenticated]
        else:
            # Otras acciones: verificar permisos específicos
            permission_classes = [permissions.IsAuthenticated]

        return [permission() for permission in permission_classes]

    def list(self, request, *args, **kwargs):
        """Override para filtrar según el grupo del usuario"""
        user_groups = request.user.groups.values_list('name', flat=True)

        # Verificar permisos manuales para listar
        if not user_groups:
            return Response(
                {"error": "Usuario sin grupos asignados"}, 
                status=status.HTTP_403_FORBIDDEN
            )

        # Los empleados solo ven clientes activos
        if 'Empleados' in user_groups:
            self.queryset = self.queryset.filter(status='ACTIVE')

        # Llamar al método padre para obtener la respuesta estándar
        return super().list(request, *args, **kwargs)

    def create(self, request, *args, **kwargs):
        """Override para validaciones de negocio y logging"""
        user_groups = request.user.groups.values_list('name', flat=True)

        # Validaciones de negocio por grupo
        if 'Gerentes' in user_groups and 'Administradores' not in user_groups:
            # Los gerentes no pueden crear clientes inactivos
            if request.data.get('status') == 'INACTIVE':
                return Response(
                    {
                        "error": "Los gerentes no pueden crear clientes inactivos",
                        "user_groups": list(user_groups)
                    },
                    status=status.HTTP_403_FORBIDDEN
                )

        response = super().create(request, *args, **kwargs)

        if response.status_code == status.HTTP_201_CREATED:
            # Log de auditoría
            print(f"✓ Cliente '{response.data.get('name')}' creado por: {request.user.username} ({', '.join(user_groups)})")

        return response

    def update(self, request, *args, **kwargs):
        """Override para validaciones de actualización"""
        user_groups = request.user.groups.values_list('name', flat=True)

        # Los gerentes no pueden cambiar status a INACTIVE
        if 'Gerentes' in user_groups and 'Administradores' not in user_groups:
            if request.data.get('status') == 'INACTIVE':
                return Response(
                    {
                        "error": "Los gerentes no pueden desactivar clientes",
                        "user_groups": list(user_groups)
                    },
                    status=status.HTTP_403_FORBIDDEN
                )

        response = super().update(request, *args, **kwargs)

        if response.status_code == status.HTTP_200_OK:
            print(f"✓ Cliente actualizado por: {request.user.username} ({', '.join(user_groups)})")

        return response

    def destroy(self, request, *args, **kwargs):
        """Override para logging de eliminaciones"""
        instance = self.get_object()
        client_name = instance.name
        user_groups = request.user.groups.values_list('name', flat=True)

        response = super().destroy(request, *args, **kwargs)

        if response.status_code == status.HTTP_204_NO_CONTENT:
            print(f"⚠️  Cliente '{client_name}' eliminado por: {request.user.username} ({', '.join(user_groups)})")

        return response

    def get_user_permissions_info(self, user):
        """Obtener información de permisos del usuario"""
        user_groups = user.groups.values_list('name', flat=True)

        return {
            'username': user.username,
            'groups': list(user_groups),
            'permissions': {
                'can_view_client': user.has_perm('client.view_client'),
                'can_add_client': user.has_perm('client.add_client'),
                'can_change_client': user.has_perm('client.change_client'),
                'can_delete_client': user.has_perm('client.delete_client'),
            },
            'is_staff': user.is_staff,
            'is_superuser': user.is_superuser
        }

    @action(detail=False, methods=['get'])
    def my_permissions(self, request):
        """Endpoint para ver los permisos del usuario autenticado"""
        return Response(self.get_user_permissions_info(request.user))

    @action(detail=False, methods=['get'], permission_classes=[IsEmployeeOrAbove])
    def statistics(self, request):
        """Estadísticas para empleados, gerentes y administradores"""
        total_clients = self.queryset.count()
        active_clients = self.queryset.filter(status='ACTIVE').count()

        return Response({
            'total_clients': total_clients,
            'active_clients': active_clients,
            'inactive_clients': total_clients - active_clients,
            'generated_by': request.user.username,
            'timestamp': timezone.now().isoformat()
        })

    @action(detail=False, methods=['get'], permission_classes=[IsAdminUser])
    def admin_only_stats(self, request):
        """Estadísticas avanzadas solo para administradores"""
        from django.contrib.auth.models import User

        return Response({
            'total_users': User.objects.count(),
            'total_clients': self.queryset.count(),
            'clients_by_status': {
                'ACTIVE': self.queryset.filter(status='ACTIVE').count(),
                'INACTIVE': self.queryset.filter(status='INACTIVE').count(),
            },
            'users_by_group': {
                group.name: group.user_set.count() 
                for group in Group.objects.all()
            },
            'generated_by': request.user.username
        })

archivo myapps/client/auth/auth.http

@baseUrl = http://localhost:8000

### ==================== AUTENTICACIÓN ====================

### 1. Login como Admin
POST {{baseUrl}}/api/auth/login/
Content-Type: application/json

{
  "username": "admin",
  "password": "admin123"
}

### 2. Login como Manager
POST {{baseUrl}}/api/auth/login/
Content-Type: application/json

{
  "username": "manager",
  "password": "manager123"
}

### 3. Login como Employee
POST {{baseUrl}}/api/auth/login/
Content-Type: application/json

{
  "username": "employee",
  "password": "employee123"
}

### 4. Login como Client
POST {{baseUrl}}/api/auth/login/
Content-Type: application/json

{
  "username": "client",
  "password": "client123"
}

### ==================== LOGOUT ====================

### 5. Logout - Invalidar refresh token
POST {{baseUrl}}/api/auth/logout/
Content-Type: application/json

{
  "refresh": "AQUI_VA_TU_REFRESH_TOKEN"
}

### 6. Verificar token
POST {{baseUrl}}/api/auth/verify/
Content-Type: application/json

{
  "token": "AQUI_VA_TU_ACCESS_TOKEN"
}

### 7. Refresh token
POST {{baseUrl}}/api/auth/refresh/
Content-Type: application/json

{
  "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc2MjI2Njk2OCwiaWF0IjoxNzYyMTgwNTY4LCJqdGkiOiI3MjQwODQxYzVlZmU0Y2Q0OTQyYjZlMDY0MDAyYzY2OSIsInVzZXJfaWQiOiIxIn0.EMRAVmY10nxHYoCRAIP7TwmJFt5bYScjS-nRu1Yb32Y"
}

archivo myapps/client/auth/client.http

### ==================== CRUD CON ADMIN ====================
@baseUrl = http://localhost:8000
@token  = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzYyMTg1MjM1LCJpYXQiOjE3NjIxODE2MzUsImp0aSI6IjZlNDg5NjEzZjhkMTRhYTg4ZDdlYjc3N2I3M2Y4Y2Y4IiwidXNlcl9pZCI6IjEifQ.Cwr93HyM0XFVXmsarhIPy_SzLgr7Hc5kvli3F6OoAno


###  READ - Listar todos los clientes (Admin)
GET {{baseUrl}}/api/clients/
Authorization: Bearer {{token}}

### READ - Ver cliente específico (Admin)
GET {{baseUrl}}/api/clients/1/
Authorization: Bearer {{token}}

### CREATE - Crear cliente
POST {{baseUrl}}/api/clients/
Authorization: Bearer {{token}}
Content-Type: application/json

{
  "name": "María García Pérez",
  "address": "Carrera 15 #45-67, Bogotá",
  "phone": "3201234567",
  "email": "maria.garcia@example.com",
  "password": "password123",
  "status": "ACTIVE"
}





### 11. UPDATE - Actualizar cliente completo (Admin)
PUT {{baseUrl}}/api/clients/1/
Authorization: Bearer {{token}}
Content-Type: application/json

{
  "name": "María García Pérez Actualizada",
  "address": "Avenida 68 #123-45, Bogotá",
  "phone": "3201111111",
  "email": "maria.garcia.updated@example.com",
  "password": "newpassword123",
  "status": "ACTIVE"
}

### 12. UPDATE - Actualización parcial (Admin)
PATCH {{baseUrl}}/api/clients/1/
Authorization: Bearer {{token}}
Content-Type: application/json

{
  "phone": "3202222222",
  "status": "INACTIVE"
}

### 13. DELETE - Eliminar cliente (Admin - debe funcionar)
DELETE {{baseUrl}}/api/clients/2/
Authorization: Bearer {{token}}