Autenticacion y Autorizacion con JWT
Para implementar autenticación y autorización con JWT Token paso a paso:
Con ViewSet
Actualizar 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:
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}}