Utilisez RemoteUserMiddleware pour faire du SSO avec Django.
Du côté de l'application Django, l'utilisateur est identifié à l'aide de la variable d'environnement REMOTE_USER définie par le module SAML (Shibboleth dans son propre environnement) en tant qu'information d'ID, mais à ce stade, les attributs autres que les informations d'ID (par exemple, l'email) sont également inclus dans la classe User du côté de l'application Django. Je veux le capturer.
Article connexe: Django + Shibboleth avec RemoteUserMiddleware (Explication de l'introduction de RemoteUserMiddleware)
--Windows Server 2016 (pas directement lié) --Apache 2.4 (pas directement lié) --Python 3.7.7 (pas directement lié)
RemoteUserBackend
recherche dans le modèle utilisateur spécifié par settings.AUTH_USER_MODEL
un objet qui a la variable d'environnement REMOTE_USER comme nom d'utilisateur. Si trouvé, connectez-vous en tant que cet utilisateur. S'il n'est pas trouvé, créez un objet Utilisateur avec REMOTE_USER comme nom d'utilisateur et connectez-vous (ce paramètre peut être modifié).
configure_user
À l'origine, RemoteUserBackend
utilise la méthode pour manipuler l'objet User lors de la création de l'objet User du côté de l'application Django.
python:django.contrib.auth.backends
class RemoteUserBackend(ModelBackend):
...
def configure_user(self, request, user):
"""
Configure a user after creation and return the updated user.
By default, return the user unmodified.
"""
return user
Créez backends.py
(n'importe quel nom) dans une application appropriée.
appname
└── appname
├── templates
├── __init__.py
├── settings.py
├── urls.py
├── views.py
├── wsgi.py
└── backends.py #← Créer
Remplacez configure_user
. Cette fois, j'ai décidé de saisir l'attribut ʻATR_mail` ajouté par Shibboleth à l'en-tête HTTP dans l'objet User.
backends.py
from django.contrib.auth.backends import RemoteUserBackend
class MyRemoteUserBackend(RemoteUserBackend):
def configure_user(self, request, user: MyUser):
user.email = request.META['ATR_mail']
user.save() #← N'oubliez pas! !!
return user
Pour activer cela, vous devez modifier ʻAUTHENTICATION_BACKENDS dans
settings.py`.
settings.py
AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.RemoteUserBackend', #← Supprimer
'appname.backends.MyRemoteUserBackend', #← Ajouter
'django.contrib.auth.backends.ModelBackend',
)
En remplaçant simplement configure_user
, configure_user
ne sera exécuté que lorsque vous créerez un nouvel objet User pour REMOTE_USER, qui est nouveau dans Django, et que vous vous connecterez. ** **
L'objet utilisateur existant n'est pas mis à jour même si les informations d'attribut du côté IdP changent, mais cela semble être gênant dans de nombreux cas. Par exemple, si vous utilisez l'authentification unique dans une entreprise et que le numéro de service ou de poste change de temps en temps.
Dans ce cas, vous devez modifier ʻauthentifier. Heureusement, vous pouvez utiliser le
MyRemoteUserBackend de
backends.pycréé précédemment. La plupart sont des copies de la superclasse
Remote User Backend ʻauthenticate
.
backends.py
from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.models import User
import inspect
import warnings
from django.contrib.auth import get_user_model
from django.utils.deprecation import RemovedInDjango31Warning
UserModel = get_user_model()
class MyRemoteUserBackend(RemoteUserBackend):
def authenticate(self, request, remote_user):
"""
The username passed as ``remote_user`` is considered trusted. Return
the ``User`` object with the given username. Create a new ``User``
object if ``create_unknown_user`` is ``True``.
Return None if ``create_unknown_user`` is ``False`` and a ``User``
object with the given username is not found in the database.
"""
if not remote_user:
return
user = None
username = self.clean_username(remote_user)
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel._default_manager.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created: #← Attention
args = (request, user)
try:
inspect.getcallargs(self.configure_user, request, user)
except TypeError:
args = (user,)
warnings.warn(
'Update %s.configure_user() to accept `request` as '
'the first argument.'
% self.__class__.__name__, RemovedInDjango31Warning
)
user = self.configure_user(*args) #← Attention
else: #← Ajouter
user = self.configure_user(request, user) #← Ajouter
else:
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
return user if self.user_can_authenticate(user) else None
def configure_user(self, request, user: User):
user.email = request.META['ATR_mail']
user.save()
return user
Comme indiqué dans "Attention", configure_user
est appelé à l'origine uniquement lorsque ʻUserModel._default_manager.get_or_create devient
created == True`. Cela ne vous permet pas de mettre à jour les informations utilisateur existantes.
Par conséquent, ajoutez deux lignes de "add" pour que configure_user
soit appelé même lorsque created! = True
. Si vous voulez séparer l'opération pour la nouvelle création et la mise à jour de l'objet User, vous pouvez imiter configure_user
et créer un nouvel ʻupdate_user` etc. pour qu'il soit appelé.
c'est tout.
Recommended Posts