- 后端
- 认证鉴权
- 身份认证
【免费下载链接】django-allauth
Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. 🔁 Mirror of https://codeberg.org/allauth/django-allauth/
django-allauth 在社交账号(social account)的登录、绑定、更新与解绑全流程中会发出若干 Django 信号(Signal),开发者可以通过接收器(receiver)在这些关键节点挂接自己的业务逻辑。本文以仓库中 docs/socialaccount/signals.rst 为骨架,结合 allauth/socialaccount/signals.py 的源码定义与触发位置,逐一讲解pre_social_login、social_account_added、social_account_updated、social_account_removed四个信号的发送时机、携带参数、典型使用场景,并给出可直接复制的订阅示例,帮助你精准地在社交认证流程中插入自定义处理。
一、信号总览:四个信号分别对应哪个业务节点
社交账号认证流程大体分为两类场景:登录/注册(用户通过第三方提供商认证后进入本地账号体系)与连接/解绑(在已登录状态下为本地账号挂接或移除第三方账号)。四个信号分别覆盖了这些流程中的四个关键节点:
| 信号 | 触发时机 | 携带参数 | 发送者(sender) |
|---|---|---|---|
pre_social_login | 用户成功通过社交提供商认证后、登录被完全处理之前 | request,sociallogin | SocialLogin |
social_account_added | 用户将社交账号连接到本地账号之后 | request,sociallogin | SocialLogin |
social_account_updated | 社交账号数据被更新之后 | request,sociallogin | SocialLogin |
social_account_removed | 用户将社交账号从本地账号解绑之后 | request,socialaccount | SocialAccount |
所有信号都定义在 allauth/socialaccount/signals.py 中,均为django.dispatch.Signal实例。注意四个信号的 sender 并不一致:前三个以SocialLogin作为 sender,而social_account_removed以SocialAccount作为 sender,订阅时需分别对应。
关于参数中的sociallogin:它并非SocialAccount模型实例,而是SocialLogin类实例。从 allauth/socialaccount/models.py 的类文档可以看到,它代表"一个正在登录过程中的社交用户",封装了account(SocialAccount实例)、token(可选的SocialToken访问令牌)、email_addresses(从提供商获取的邮箱列表)以及state等中间状态。因此你在pre_social_login中能够同时拿到访问令牌与用户画像信息。
二、pre_social_login:登录前的最后干预点
2.1 信号语义
allauth.socialaccount.signals.pre_social_login(request, sociallogin)官方文档的描述是:该信号在用户成功通过社交提供商认证之后、登录流程被完全处理之前发送。它作为社交登录(login)、注册(signup)流程的一部分发出,同样也会在为已有账号连接额外社交账号时发出。如果提供商支持,访问令牌(access token)和资料信息(profile information)都会随sociallogin一并提供。
2.2 源码中的发送位置与调用链
该信号的发送位于 allauth/socialaccount/internal/flows/login.py 的pre_social_login()函数中:
def pre_social_login(request: HttpRequest, sociallogin: SocialLogin) -> None: clear_pending_signup(request) assert not sociallogin.is_existing # nosec sociallogin.lookup() get_adapter().pre_social_login(request, sociallogin) signals.pre_social_login.send( sender=SocialLogin, request=request, sociallogin=sociallogin )注意几个细节:
- 发送信号前会先调用
sociallogin.lookup(),即尝试按(provider, uid)查找是否已有对应的本地SocialAccount(见_lookup_by_socialaccount),找不到再按邮箱匹配(_lookup_by_email)。所以你在信号处理器中读取sociallogin.is_existing时,能判断这是"老用户再次登录"还是"全新用户"。 - 该函数被
complete_login()调用(同文件 login.py),随后根据sociallogin.state中的process值分派到REDIRECT(纯跳转)、CONNECT(连接已有账号)或默认的_authenticate(登录/注册)流程。这意味着信号在流程分派之前发出,无论最终走向登录还是连接,pre_social_login都会先被触发。
2.3 典型用途
从 allauth/socialaccount/adapter.py 中同名 adapter 钩子的注释可以看到官方建议的两类典型用法:
- 拦截登录:例如对特定提供商、特定邮箱域名的用户禁止登录,通过抛出
ImmediateHttpResponse来中止流程并返回自定义响应; - 在登录落地前做业务预处理:例如记录第三方会话、校验黑白名单、注入自定义用户数据等。
# receivers.py from django.dispatch import receiver from allauth.socialaccount.signals import pre_social_login @receiver(pre_social_login) def on_pre_social_login(sender, request, sociallogin, **kwargs): # 例如:阻止来自特定提供商的登录 if sociallogin.account.provider == "blocked_provider": from django.core.exceptions import PermissionDenied raise PermissionDenied("This provider is not allowed.")信号处理器与 adapter 钩子同时存在的原因,官方在 adapter 注释中给出明确说明:从信号处理器内部干预流程是不好的做法——因为可能挂载了多个处理器,且它们的执行顺序不确定。因此,如果你需要在登录前中止流程,应当覆写 adapter 的pre_social_login()钩子(它先于信号执行);如果你只是需要观察/记录而不干预,或者需要多个模块各自响应,则应使用信号。
三、social_account_added:账号绑定成功之后
allauth.socialaccount.signals.social_account_added(request, sociallogin)该信号在用户将社交账号连接到本地账号之后发出。官方文档特别强调:这是一个显式动作(即用户主动执行"连接"操作),不会因为社交账号的创建而自动调用——换言之,首次通过社交登录自动注册生成SocialAccount时不会触发此信号。
3.1 源码中的发送位置
发送逻辑位于 allauth/socialaccount/models.py 的SocialLogin.connect()方法中:
def connect(self, request: HttpRequest, user: AbstractBaseUser) -> None: self.user = user self.save(request, connect=True) signals.social_account_added.send( sender=SocialLogin, request=request, sociallogin=self ) get_adapter().send_notification_mail( "socialaccount/email/account_connected", self.user, context={ "account": self.account, "provider": self.account.get_provider(), }, )connect()的调用入口是 allauth/socialaccount/internal/flows/connect.py 的do_connect():当用户已登录且社交账号尚未绑定到任何本地用户时,会走sociallogin.connect(request, request.user)分支。注意信号发送后还会发送一封"账号已连接"的通知邮件(模板socialaccount/email/account_connected)。
3.2 典型用途
绑定成功后往往需要做数据迁移或初始化,例如:
from django.dispatch import receiver from allauth.socialaccount.signals import social_account_added @receiver(social_account_added) def on_social_account_added(sender, request, sociallogin, **kwargs): # 从提供商资料中抽取头像并保存到本地用户 extra_data = sociallogin.account.extra_data avatar_url = extra_data.get("avatar_url") if avatar_url and sociallogin.user: sociallogin.user.profile.avatar_url = avatar_url sociallogin.user.profile.save()四、social_account_updated:数据刷新的通知
allauth.socialaccount.signals.social_account_updated(request, sociallogin)该信号在社交账号数据被更新之后发出。官方文档指出两种情况会触发:
- 用户使用一个已经连接过的社交账号登录;
- 用户对已连接的社交账号再次执行 connect 流程。
对处理extra_data很有价值——官方文档原文建议:"如果你需要为社交账号在更新时解包(unpack)额外的数据,这个信号很有用"。
4.1 源码中的发送位置
发送逻辑位于 allauth/socialaccount/models.py 的SocialLogin._lookup_by_socialaccount()中:
def _lookup_by_socialaccount(self) -> bool: assert not self.is_existing # nosec try: a = SocialAccount.objects.get( provider=self.account.provider, uid=self.account.uid ) # Update account a.extra_data = self.account.extra_data self.account = a self.user = self.account.user a.save() signals.social_account_updated.send( sender=SocialLogin, request=context.request, sociallogin=self ) self._store_token() return True except SocialAccount.DoesNotExist: return False当用户在登录时,系统发现(provider, uid)已对应一个本地SocialAccount,会把提供商返回的最新extra_data写回数据库并保存,随后发出该信号,并继续更新SocialToken(_store_token)。也就是说,该信号是"提供商侧资料已同步到本地"的回执。
4.2 典型用途
from django.dispatch import receiver from allauth.socialaccount.signals import social_account_updated @receiver(social_account_updated) def on_social_account_updated(sender, request, sociallogin, **kwargs): # 每次用户通过社交账号登录后,同步最新资料到本地 email = sociallogin.account.extra_data.get("email") if email and sociallogin.user: sociallogin.user.email = email sociallogin.user.save()五、social_account_removed:解绑完成之后
allauth.socialaccount.signals.social_account_removed(request, socialaccount)该信号在用户将社交账号从本地账号断开之后发出。注意这里的第二个参数是socialaccount(SocialAccount实例),而不是sociallogin,sender 也是SocialAccount。
5.1 源码中的发送位置
发送逻辑位于 allauth/socialaccount/internal/flows/connect.py 的disconnect()函数中:
def disconnect(request: HttpRequest, account) -> None: if account_settings.REAUTHENTICATION_REQUIRED: flows.reauthentication.raise_if_reauthentication_required(request) get_account_adapter().add_message( request, messages.INFO, "socialaccount/messages/account_disconnected.txt", ) provider = account.get_provider() account.delete() signals.social_account_removed.send( sender=SocialAccount, request=request, socialaccount=account ) get_adapter().send_notification_mail( "socialaccount/email/account_disconnected", request.user, context={ "account": account, "provider": provider, }, )顺序值得注意:先删除SocialAccount记录(account.delete()),再发送信号,最后发送"账号已断开"通知邮件。因此在信号处理器中,该SocialAccount已经不在数据库中了,你拿到的是删除前的内存快照。若需要完整资料,请在删除前自行缓存。
此外,disconnect()之前还有validate_disconnect()做安全校验(同文件 connect.py):如果这是用户最后一个第三方账号,且SOCIALACCOUNT_ONLY开启、用户没有可用密码或没有已验证邮箱,解绑会被拒绝并抛出校验错误——这些校验失败时social_account_removed自然不会发出。
5.2 典型用途
from django.dispatch import receiver from allauth.socialaccount.signals import social_account_removed @receiver(social_account_removed) def on_social_account_removed(sender, request, socialaccount, **kwargs): # 清理与第三方账号绑定的本地缓存数据 provider = socialaccount.provider uid = socialaccount.uid # 例如:注销远程回调、清理用户本地画像中的第三方关联字段 request.user.profile.connected_providers.remove(provider) request.user.profile.save()六、订阅信号的三种推荐姿势
Django 信号的订阅本质是调用signal.connect(receiver_func)。推荐以下两种方式之一注册:
方式一:使用@receiver装饰器 + AppConfig.ready()
# myapp/signals.py from django.dispatch import receiver from allauth.socialaccount import signals @receiver(signals.pre_social_login) def my_handler(sender, request, sociallogin, **kwargs): ...# myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "myapp" def ready(self): import myapp.signals # noqa: F401方式二:显式 connect(适合在既有模块中快速挂接)
from allauth.socialaccount import signals def on_removed(sender, request, socialaccount, **kwargs): ... signals.social_account_removed.connect(on_removed)方式三:通过dispatch_uid防止重复注册
在 Django 的 autoreload / 测试环境中,ready()可能被多次调用,重复注册会导致处理器被多次执行。为稳妥起见可指定dispatch_uid:
@receiver(signals.pre_social_login, dispatch_uid="myapp.on_pre_social_login") def my_handler(sender, request, sociallogin, **kwargs): ...七、测试验证:信号确实按预期触发
仓库测试 tests/apps/socialaccount/test_login.py 提供了一个很好的信号验证模式:用unittest.mock.patch替换信号对象的.send方法,再断言是否被调用:
with patch("allauth.socialaccount.signals.social_account_updated.send") as updated_signal: with patch("allauth.socialaccount.signals.social_account_added.send") as added_signal: resp = complete_social_login(request, sociallogin) assert added_signal.called == auto_connect assert not updated_signal.called该测试验证了邮件认证场景下的行为:当SOCIALACCOUNT_EMAIL_AUTHENTICATION关闭时,added_signal与updated_signal均不被调用;开启且自动连接(auto_connect)时,只有social_account_added被调用,social_account_updated不被调用。这个思路可以直接复用到你自己的测试中,用于确认你的业务代码挂在正确的信号上、且触发次数符合预期。
八、小结:信号选择速查
| 你的需求 | 应订阅的信号 | 注意事项 |
|---|---|---|
| 登录/注册/连接发生前做统一预处理或拦截 | pre_social_login | 需干预流程时优先用 adapter 钩子;参数为sociallogin |
| 用户主动绑定第三方账号成功后做初始化 | social_account_added | 首次社交注册自动建号不触发;参数为sociallogin |
| 每次通过已绑定账号登录后同步最新资料 | social_account_updated | 此时extra_data已写回数据库;参数为sociallogin |
| 用户解绑第三方账号后做清理 | social_account_removed | 记录已被删除,只有内存快照;参数为socialaccount |
四个信号共同覆盖了社交账号从认证、绑定、数据更新到解绑的完整生命周期。正确理解它们的触发时机与参数差异,就能在不改动 allauth 源码的前提下,通过标准的 Django 信号机制将你的业务逻辑精准地挂接到认证流程的每一个关键节点上。若需要进一步了解模型与流程细节,可继续阅读 allauth/socialaccount/models.py、allauth/socialaccount/internal/flows/login.py 与 allauth/socialaccount/internal/flows/connect.py。
- 后端
- 认证鉴权
- 身份认证
【免费下载链接】django-allauth
Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. 🔁 Mirror of https://codeberg.org/allauth/django-allauth/
相关推荐
Tolaria 将 Claude CLI 作为子进程驱动 AI Agent:NDJSON 流式事件与 MCP 工具集成的工程实践
Tolaria 将 Claude CLI 作为子进程驱动 AI Agent:NDJSON 流式事件与 MCP 工具集成的工程实践 本文基于 Tolaria 仓库
后端认证鉴权身份认证终极指南:3种方法在iOS设备间启用JIT调试
终极指南:3种方法在iOS设备间启用JIT调试 你是否曾遇到过iOS设备上的JIT(即时编译)限制问题?传统的iOS开发调试往往需要Xcode和USB连接,但在
后端认证鉴权身份认证Sanic 信号机制(Signals)完整指南:事件、分发与内置生命周期信号
Sanic 信号机制(Signals)完整指南:事件、分发与内置生命周期信号 信号(Signals)是 Sanic 应用内实现"一处发生、多处感知"的解耦通信机
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考