Mise en œuvre de l'authentification asnychrone via AJAX
Pour établir un mécanisme de connexion sans rechargement de page, nous configurons une application Django. Commencez par définir les routes dans le fichier de configuration des URL.
from application import controleurs
urlpatterns = [
path('administration/', admin.site.urls),
path('connexion/', controleurs.page_connexion),
path('tableau-de-bord/', controleurs.page_accueil),
]
Ensuite, ajustez les paramètres pour servir correctement les fichiers statiques.
STATIC_URL = '/statiques/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'statiques'),
)
Développez les vues pour gérer la logique d'authentification et la redirection.
from django.shortcuts import render, HttpResponse
import json
def page_accueil(requete):
return HttpResponse('Bienvenue sur le tableau de bord')
def page_connexion(requete):
if requete.method == "POST":
identifiant = requete.POST.get("identifiant")
mot_de_passe = requete.POST.get("mot_de_passe")
resultat = {"etat": 0, 'destination': ''}
if identifiant == "administrateur" and mot_de_passe == "secret123":
resultat['etat'] = 1
resultat['destination'] = '/tableau-de-bord/'
return HttpResponse(json.dumps(resultat))
return render(requete, "formulaire_connexion.html")
Créez le template HTML intégrant le script AJAX pour soumettre les identifiants.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Authentification</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<form id="form-connexion" class="w-50 mx-auto">
{% csrf_token %}
<h2 class="mb-4">Connexion au système</h2>
<div class="form-group">
<label for="champ-identifiant">Identifiant</label>
<input type="text" id="champ-identifiant" class="form-control" name="identifiant" required>
</div>
<div class="form-group">
<label for="champ-mot-de-passe">Mot de passe</label>
<input type="password" id="champ-mot-de-passe" class="form-control" name="mot_de_passe" required>
</div>
<button type="button" id="btn-valider" class="btn btn-primary btn-block">Se connecter</button>
</form>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$('#btn-valider').click(function () {
$.ajax({
url: '/connexion/',
type: 'post',
data: {
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
identifiant: $('[name="identifiant"]').val(),
mot_de_passe: $('[name="mot_de_passe"]').val()
},
success: function (reponse) {
let donnees = JSON.parse(reponse);
if (donnees.etat === 1) {
window.location.href = donnees.destination;
} else {
alert('Identifiants incorrects');
}
}
});
});
</script>
</body>
</html>
Protection contre les attaques CSRF
Lors de l'envoi de requêtes POST asynchrones, Django exige un jeton de sécurité. Plutôt que de l'injecter directement dans le corps de la requête, il est préférable de l'inclure dans les en-têtes HTTP via une configuration globale de jQuery.
function methodeHttpSecurisee(methode) {
// Les méthodes suivantes ne nécessitent pas de protection CSRF
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(methode));
}
$.ajaxSetup({
beforeSend: function (xhr, parametres) {
if (!methodeHttpSecurisee(parametres.type) && !this.crossDomain) {
// Extraction du jeton depuis les cookies et injection dans l'en-tête
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
});
Cette approche garantit que toutes les requêtes AJAX sortantes sont automatiquement sécurisées sans avoir à modifier chaque appel individuellement.
Fonctionnement et création de Middlewares
Un middleware agit comme un intercepteur au niveau du framework, permettant de traiter les requêtes entrantes et les réponses sortantes de manière centralisée. Il est particulièrement utile pour des tâches transversales comme la journalisation ou le contrôle d'accès.
Voici l'implémentation d'un middleware personnalisé pour restreindre l'accès aux routes non autorisées :
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect, HttpResponse
class ControleAccesMiddleware(MiddlewareMixin):
chemins_autorises = ['/connexion/', '/statiques/']
chemins_bloques = ['/zone-restreinte/']
def process_request(self, requete):
chemin_cible = requete.path_info
# Blocage immédiat pour les chemins interdits
if chemin_cible in self.chemins_bloques:
return HttpResponse("Accès interdit par le système", status=403)
# Autorisation si le chemin est public ou si l'utilisateur est authentifié
if chemin_cible in self.chemins_autorises or requete.session.get("utilisateur_connecte"):
return None
# Redirection vers la page de connexion pour les autres cas
return redirect('/connexion/')
def process_response(self, requete, reponse):
# Ajout d'un en-tête de sécurité personnalisé
reponse['X-Frame-Options'] = 'DENY'
return reponse
Pour activer ce composant, il doit être enregistré dans la liste MIDDLEWARE du fichier de configuration. L'ordre d'enregistrement est crucial : les requêtes traversent les middlewares de haut en bas, tandis que les réponses remontent de bas en haut.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'application.middlewares.ControleAccesMiddleware', # Middleware personnalisé
]
Développement d'un composant de pagination personnalisé
Bien que Django fournisse un paginateur intégré, la création d'une classe sur mesure offre une flexibilité totale sur le rendu HTML et la logique de découpage des données.
class GenerateurDePagination:
def __init__(self, page_courante, total_elements, taille_page=10, fenetre_affichage=11):
self.page_courante = page_courante
self.total_elements = total_elements
self.taille_page = taille_page
self.fenetre_affichage = fenetre_affichage
self.demi_fenetre = self.fenetre_affichage // 2
self.total_pages, reste = divmod(total_elements, taille_page)
if reste > 0:
self.total_pages += 1
# Normalisation de la page courante
if self.page_courante > self.total_pages:
self.page_courante = self.total_pages
elif self.page_courante < 1:
self.page_courante = 1
@property
def indice_debut(self):
return (self.page_courante - 1) * self.taille_page
@property
def indice_fin(self):
return self.page_courante * self.taille_page
@property
def code_html(self):
debut_fenetre = self.page_courante - self.demi_fenetre
fin_fenetre = self.page_courante + self.demi_fenetre + 1
if self.page_courante + self.demi_fenetre >= self.total_pages:
fin_fenetre = self.total_pages + 1
debut_fenetre = self.total_pages - self.fenetre_affichage + 1
if self.page_courante <= self.demi_fenetre:
debut_fenetre = 1
fin_fenetre = self.fenetre_affichage + 1
elements_html = []
elements_html.append('<li><a href="?page=1">Premier</a></li>')
page_precedente = self.page_courante - 1
if page_precedente < 1:
elements_html.append('<li class="disabled"><a href="#">Précédent</a></li>')
else:
elements_html.append(f'<li><a href="?page={page_precedente}">Précédent</a></li>')
for numero in range(debut_fenetre, fin_fenetre):
if numero == self.page_courante:
elements_html.append(f'<li class="active"><a href="?page={numero}">{numero}</a></li>')
else:
elements_html.append(f'<li><a href="?page={numero}">{numero}</a></li>')
page_suivante = self.page_courante + 1
if page_suivante > self.total_pages:
elements_html.append('<li class="disabled"><a href="#">Suivant</a></li>')
else:
elements_html.append(f'<li><a href="?page={page_suivante}">Suivant</a></li>')
elements_html.append(f'<li><a href="?page={self.total_pages}">Dernier</a></li>')
return ''.join(elements_html)
Intégration de cette classe dans le contrôleur de la vue :
from django.shortcuts import render
from .pagination import GenerateurDePagination
def liste_utilisateurs(requete):
# Simulation d'un jeu de données
base_de_donnees = [{'nom': f'Utilisateur_{i}', 'age': 20 + (i % 40)} for i in range(1, 302)]
try:
numero_page = int(requete.GET.get('page', 1))
except ValueError:
numero_page = 1
paginateur = GenerateurDePagination(numero_page, len(base_de_donnees))
donnees_pagees = base_de_donnees[paginateur.indice_debut:paginateur.indice_fin]
contexte = {
'liste_utilisateurs': donnees_pagees,
'navigation': paginateur.code_html
}
return render(requete, 'liste_utilisateurs.html', contexte)
Le template se charge ensuite d'itérer sur les données et d'injecter la navigation générée :
<div class="container">
<table class="table table-striped">
<thead>
<tr><th>Nom</th><th>Âge</th></tr>
</thead>
<tbody>
{% for utilisateur in liste_utilisateurs %}
<tr>
<td>{{ utilisateur.nom }}</td>
<td>{{ utilisateur.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav aria-label="Navigation des pages">
<ul class="pagination justify-content-center">
{{ navigation|safe }}
</ul>
</nav>
</div>