尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

django-allauth 社交登录适配器(DefaultSocialAccountAdapter)完整指南:从配置到方法级定制

django-allauth 社交登录适配器(DefaultSocialAccountAdapter)完整指南:从配置到方法级定制 后端认证鉴权身份认证【免费下载链接】django-allauthIntegrated 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/项目地址https://gitcode.com/gh_mirrors/dj/django-allauth点击查看免费下载导读本文聚焦 django-allauth 中allauth.socialaccount应用的**适配器Adapter**机制以官方文档 docs/socialaccount/adapter.rst 指向的DefaultSocialAccountAdapter为核心系统讲解如何通过SOCIALACCOUNT_ADAPTER设置替换默认适配器从而定制社交登录social login、自动注册auto signup、邮箱认证email authentication、应用app发现等行为。读完本文你将掌握适配器的全部可覆写方法、底层调用链与官方测试用例的验证方式能够在自己的 Django 项目中精准定制第三方账号登录流程。一、适配器是什么为什么社交登录需要 Adapterdjango-allauth 的socialaccount应用负责与 Google、GitHub、Facebook 等第三方提供商provider交互。不同站点对登录流程的需求差异极大有的需要拦截登录前事件、有的需要改注册页预填数据、有的需要限制可用的 provider 实例。为避免把这些定制逻辑硬编码进视图层django-allauth 采用了适配器模式所有可定制的行为都收敛到一个适配器类上开发者只需继承默认实现并覆写感兴趣的方法。从源码看DefaultSocialAccountAdapter继承自BaseAdapter见 allauth/core/internal/adapter.py。BaseAdapter提供两个基础设施一是构造函数将当前请求挂到self.request显式传request参数已废弃直接使用allauth.core.context.request二是提供validation_error(code, *args)方法用于从error_messages字典中取出错误模板并抛出ValidationError# allauth/core/internal/adapter.py class BaseAdapter: error_messages: dict def __init__(self, request: HttpRequest | None None) - None: # Explicitly passing request is deprecated, just use: # allauth.core.context.request. self.request context.request def validation_error(self, code, *args) - ValidationError: message self.error_messages[code] if args: message message % args exc ValidationError(message, codecode) return exc而默认适配器的定位在 allauth/socialaccount/adapter.py 的类文档字符串中写得很清楚将settings.SOCIALACCOUNT_ADAPTER指向你自己的、继承自DefaultSocialAccountAdapter的类并按需覆写方法即可改变allauth.socialaccount应用的默认行为。二、如何启用自定义适配器SOCIALACCOUNT_ADAPTER 设置2.1 配置入口官方配置文档 docs/socialaccount/configuration.rst 给出了唯一入口设置SOCIALACCOUNT_ADAPTER (default: allauth.socialaccount.adapter.DefaultSocialAccountAdapter) 指定适配器类允许你改变某些默认行为。默认值在 allauth/socialaccount/app_settings.py 的ADAPTER属性中定义property def ADAPTER(self) - str: return self._setting( ADAPTER, allauth.socialaccount.adapter.DefaultSocialAccountAdapter, )所有SOCIALACCOUNT_*设置均通过AppSettings类以SOCIALACCOUNT_为前缀从 Django settings 读取。2.2 模块级工厂函数get_adapter()是使用适配器的统一入口allauth/socialaccount/adapter.pydef get_adapter(request: HttpRequest | None None) - DefaultSocialAccountAdapter: return import_attribute(app_settings.ADAPTER)(request)它通过import_attribute按字符串导入SOCIALACCOUNT_ADAPTER指定的类并实例化。这意味着你在设置中配置的字符串路径可以是项目中任意可导入的类。2.3 官方测试用例一个最小的自定义适配器测试目录 tests/apps/socialaccount/test_adapter.py 给出了最简洁的覆写范例——给 OAuth 的state参数加前缀class PrefixStateSocialAccountAdapter(DefaultSocialAccountAdapter): def generate_state_param(self, state: dict) - str: return fprefix-{super().generate_state_param(state)} def test_generate_state_param(settings, client, db, google_provider_settings): settings.SOCIALACCOUNT_ADAPTER ( tests.apps.socialaccount.test_adapter.PrefixStateSocialAccountAdapter ) resp client.post(reverse(google_login)) parsed urlparse(resp[location]) query parse_qs(parsed.query) state query[state][0] assert len(state) len(prefix-) statekit.STATE_ID_LENGTH assert state.startswith(prefix-)这个测试同时验证了两点适配器类可以以字符串路径形式配置generate_state_param的返回值确实会进入 provider 重定向 URL 的state参数。三、默认错误消息error_messagesDefaultSocialAccountAdapter定义了一组内置错误消息allauth/socialaccount/adapter.py这些消息配合BaseAdapter.validation_error()使用在社交账号连接/断开等场景中抛出错误码默认消息触发场景email_taken已存在使用该邮箱的账号请先登录该账号再绑定你的 %s 账号邮箱已被占用invalid_tokenInvalid token.token 校验失败no_password你的账号未设置密码断开最后一个社交账号时本地账号无可用密码no_verified_email你的账号没有已验证的邮箱强制邮箱验证时无已验证邮箱disconnect_last你不能断开最后一个第三方账号SOCIALACCOUNT_ONLY模式下尝试断开唯一社交账号connected_other该第三方账号已连接到其他账号社交账号已被他人绑定其中disconnect_last、no_password、no_verified_email的具体判定逻辑位于 allauth/socialaccount/internal/flows/connect.py 的validate_disconnect()is_last not accounts.exclude(pkaccount.pk).exists() adapter get_adapter() if is_last: if allauth_settings.SOCIALACCOUNT_ONLY: raise adapter.validation_error(disconnect_last) if not account.user.has_usable_password(): raise adapter.validation_error(no_password) if (account_settings.EMAIL_VERIFICATION account_settings.EmailVerificationMethod.MANDATORY): if not EmailAddress.objects.filter( useraccount.user, verifiedTrue ).exists(): raise adapter.validation_error(no_verified_email) adapter.validate_disconnect(account, accounts)四、登录生命周期钩子pre_social_login 与 on_authentication_error这两个钩子分别对应认证成功后、登录处理前与认证出错时两个节点。4.1 pre_social_login在登录落地前介入调用位置在 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( senderSocialLogin, requestrequest, socialloginsociallogin )注意执行顺序适配器钩子先于pre_social_login信号被触发。源码注释解释了原因——多个信号处理器会以不确定顺序执行若在信号处理器里干预流程例如抛异常中止登录会很糟糕适配器钩子则是单一、可控的干预点。默认实现为空pass。典型用法是抛出ImmediateHttpResponse来中止登录例如强制新用户先看一遍服务条款再完成登录。核心异常类型定义于 allauth/core/exceptions.py由complete_login()allauth/socialaccount/internal/flows/login.py统一捕获并返回对应响应。4.2 on_authentication_error处理认证失败默认实现包含一处向后兼容逻辑若旧版适配器定义了已废弃的authentication_error方法则自动转发并发出弃用警告def on_authentication_error(self, request, provider, errorNone, exceptionNone, extra_contextNone) - None: if hasattr(self, authentication_error): warnings.warn( adapter.authentication_error() is deprecated, use adapter.on_authentication_error() ) self.authentication_error( request, provider.id, errorerror, exceptionexception, extra_contextextra_context)因此自定义适配器应实现新方法on_authentication_error而不是旧的authentication_error。五、用户创建与注册流程new_user / populate_user / save_user5.1 new_user实例化新用户def new_user(self, request, sociallogin): return get_account_adapter().new_user(request)默认转交给allauth.account的适配器来创建用户实例保证本地账号体系的一致性。5.2 populate_user填充用户字段默认实现从 provider 返回的data中提取username、first_name、last_name、email、name填充到用户实例allauth/socialaccount/adapter.pyusername data.get(username) first_name data.get(first_name) last_name data.get(last_name) email data.get(email) name data.get(name) user_username(user, username or ) user_email(user, valid_email_or_none(email) or ) name_parts (name or ).partition( ) user_field(user, first_name, first_name or name_parts[0]) user_field(user, last_name, last_name or name_parts[2])值得注意的细节当 provider 只给了一个name如 Ada Lovelace而没有单独的 first/last name 时会通过partition( )拆出first_name和last_name。同时源码明确说明这个用户实例只是建议值不必完全合法、也不必保证无冲突例如用户名是否已存在不是这里的职责因为后续注册流程还会做唯一性校验。5.3 save_user保存新注册的社交用户def save_user(self, request, sociallogin, formNone): u sociallogin.user u.set_unusable_password() account_adapter get_account_adapter() if form: account_adapter.save_user(request, u, form) else: account_adapter.populate_username(request, u) sociallogin.save(request) return u两个关键行为set_unusable_password()社交注册的用户默认没有本地密码这是安全设计后续可通过设置密码流程补上form参数区分两条路径自动注册auto signup时无表单只调用populate_username保证用户名存在手动填写注册表单时则走account_adapter.save_user应用表单数据。该方法的调用链在 allauth/socialaccount/internal/flows/signup.py 的process_signup()if not auto_signup: resp redirect_to_signup(request, sociallogin) else: # ... username 冲突时清空 username ... get_adapter().save_user(request, sociallogin, formNone) resp complete_social_signup(request, sociallogin)5.4 get_signup_form_initial_data预填注册表单当自动注册不可行、需要用户手动填写注册表单时该方法提供表单的初始值def get_signup_form_initial_data(self, sociallogin) - dict: user sociallogin.user email user_email(user) if not email and len(sociallogin.email_addresses) 0: email sociallogin.email_addresses[0].email initial { email: email or , username: user_username(user) or , first_name: user_field(user, first_name) or , last_name: user_field(user, last_name) or , } return initial注意优先级优先使用sociallogin.user.email如果用户实例上没有邮箱则回退到sociallogin.email_addresses列表中的第一个邮箱。官方测试 tests/apps/socialaccount/test_adapter.py 的test_get_signup_form_initial_data验证了这条回退逻辑当sociallogin.user.email为空时initial_data[email]取自sociallogin.email_addresses。六、注册开关is_auto_signup_allowed 与 is_open_for_signup6.1 is_auto_signup_allowed是否允许自动注册def is_auto_signup_allowed(self, request, sociallogin) - bool: # If email is specified, check for duplicate and if so, no auto signup. auto_signup app_settings.AUTO_SIGNUP return auto_signup默认值来自SOCIALACCOUNT_AUTO_SIGNUP默认True见 allauth/socialaccount/app_settings.py。当设置为True时django-allauth 尝试绕过注册表单直接用 provider 返回的 username、email 等字段完成注册但若出现邮箱冲突注册表单仍会出现。真实判定逻辑在 allauth/socialaccount/internal/flows/signup.py 的process_auto_signup_email()中邮箱唯一assess_unique_email返回True→ 自动注册继续邮箱已被他人占用 → 强制转注册表单开启了防枚举prevent enumeration→ 表现为发送账号已存在邮件并走prevent_enumeration响应SOCIALACCOUNT_EMAIL_REQUIRED为真但拿不到邮箱 → 转注册表单。此外allauth/socialaccount/internal/flows/signup.py 的process_auto_signup_phone()还处理了手机号场景注册字段要求手机号且手机号已被占用时同样关闭自动注册。6.2 is_open_for_signup站点是否开放注册def is_open_for_signup(self, request, sociallogin) - bool: return get_account_adapter(request).is_open_for_signup(request)默认复用allauth.account适配器的判定。若返回Falseprocess_signup()会抛出SignupClosedException最终由complete_login()捕获并渲染account/signup_closed.html模板参见 allauth/socialaccount/internal/flows/login.py。同样地你也可以在此抛出ImmediateHttpResponse干预流程。七、provider 与应用发现list_providers / get_provider / list_apps / get_appSocialApp可以存储在数据库中也可以通过SOCIALACCOUNT_PROVIDERS设置在配置文件中声明。这一组方法负责把两种来源融合成统一的 provider/app 视图。7.1 list_apps合并数据库与配置中的应用allauth/socialaccount/adapter.py 的实现分两步数据库应用SocialApp.objects.on_site(request)获取当前站点应用request为None时取全部支持按provider、provider_id、client_id过滤配置应用遍历SOCIALACCOUNT_PROVIDERS读取每个 provider 的APP或APPS配置块构造内存态SocialApp实例字段支持name、provider_id、client_id、secret、key、settings。若配置里出现顶层certificate_key会发出警告并建议将其移入app.settings。官方测试 tests/apps/socialaccount/test_adapter.py 分别用test_list_db_based_apps和test_list_settings_based_apps验证了两种来源都能被list_apps()正确返回。7.2 get_app在多个应用中挑选唯一应用当同一 provider 配置了多个应用多租户场景时get_app()按client_id过滤若仍多于一个则优先筛选settings.get(hidden)为假的应用仍不唯一则抛MultipleObjectsReturned一个都没有则抛SocialApp.DoesNotExist。7.3 get_provider解析 provider含子 providerdef get_provider(self, request, provider, client_idNone): provider_class registry.get_class(provider) if provider_class is None or provider_class.uses_apps: app self.get_app(request, providerprovider, client_idclient_id) if not provider_class: # In this case, the provider argument passed was a provider_id. provider_class registry.get_class(app.provider) ... return provider_class(request, appapp) elif provider_class: assert not provider_class.uses_apps # nosec return provider_class(request, appNone) else: raise ImproperlyConfigured(funknown provider: {provider})要点provider参数既可以传 provider 的类标识也可以传provider_id如 SAML/OIDC 场景下的具体 IDP 标识从而实现子 provider 查找不依赖SocialApp的 provider 类uses_apps为假则直接构造。7.4 list_providers获取可用的 provider 列表allauth/socialaccount/adapter.py 遍历 provider 注册表registry.get_class_list()再与list_apps()的结果按provider分组匹配为每个provider, app组合构造 provider 实例并返回。八、邮箱验证与邮箱认证is_email_verified / can_authenticate_by_email / authenticate_by_email8.1 is_email_verified判断邮箱是否可视为已验证def is_email_verified(self, provider, email) - bool: verified_email None if provider.app: verified_email provider.app.settings.get(verified_email) if verified_email is None: settings provider.get_settings() verified_email settings.get(VERIFIED_EMAIL, False) if isinstance(verified_email, bool): pass elif isinstance(verified_email, list): email_domain email.partition()[2].lower() verified_domains [d.lower() for d in verified_email] verified_email email_domain in verified_domains else: raise ImproperlyConfigured(verified_email wrongly configured) return verified_email取值优先级provider 应用级设置verified_email在app.settings中→ 全局SOCIALACCOUNT_PROVIDERS中的VERIFIED_EMAIL→ 默认False。配置值可以是布尔值也可以是域名列表——列表模式下邮箱域名命中列表即视为已验证例如SOCIALACCOUNT_PROVIDERS { google: { VERIFIED_EMAIL: [gmail.com, googlemail.com] } }8.2 can_authenticate_by_email / authenticate_by_email邮箱认证EMAIL_AUTHENTICATION默认False相关的能力在此实现allauth/socialaccount/adapter.pydef can_authenticate_by_email(self, login, email) - bool: ret None provider login.provider if provider.app: ret provider.app.settings.get(email_authentication) if ret is None: ret app_settings.EMAIL_AUTHENTICATION or provider.get_settings().get( EMAIL_AUTHENTICATION, False) return ret or False def authenticate_by_email(self, sociallogin): emails [e.email for e in sociallogin.email_addresses if e.verified] for email in emails: if not self.can_authenticate_by_email(sociallogin, email): continue users filter_users_by_email(email, prefer_verifiedTrue) if users: return users[0], email return None语义详见 docs/socialaccount/configuration.rst 对SOCIALACCOUNT_EMAIL_AUTHENTICATION的说明当社交登录携带已被 provider 验证的邮箱而该邮箱已被本地账号占用且该账号未绑定任何社交账号时若 provider 完全可信可视为对该本地账号的直接登录。此功能默认关闭因为不可信的 provider 可以伪造社交账号数据登录任意本地账号只建议对完全可信的 provider 开启也可按 provider 单独开启SOCIALACCOUNT_PROVIDERS { google: { EMAIL_AUTHENTICATION: True } }配套设置SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT默认False控制邮箱认证成功后是否自动把该社交账号绑定到本地账号。若为False账号关系不变——这也意味着SOCIALACCOUNT_STORE_TOKENS在此场景下无法存储 token相关账号未被保存。邮箱认证流程中的安全细节在 allauth/socialaccount/internal/flows/email_authentication.py 的wipe_password()中有体现若被匹配的邮箱地址在本地未经验证则清空该账号密码并结束其他会话防止攻击者通过先注册未验证邮箱账号、再等受害者邮箱登录的方式共享账号。九、连接与断开get_connect_redirect_url / validate_disconnect9.1 get_connect_redirect_url连接成功后的跳转def get_connect_redirect_url(self, request, socialaccount) - str: url reverse(socialaccount_connections) return url默认跳转到社交账号连接管理页面socialaccount_connections。在 allauth/socialaccount/internal/flows/connect.py 的connect()与do_connect()中该 URL 既是默认跳转目标也是PermissionDenied异常时的兜底重定向地址。9.2 validate_disconnect断开前的自定义校验def validate_disconnect(self, account, accounts) - None: pass默认不做事内置的最后一个账号不可断开等检查见第三节已在调用方validate_disconnect()allauth/socialaccount/internal/flows/connect.py完成。你可以在此覆写追加自己的业务规则如 VIP 用户禁止解绑。十、其余基础设施方法方法默认行为用途deserialize_instance(model, data)/serialize_instance(instance)委托给allauth.core.internal.modelkit会话中序列化/反序列化模型实例如登录中暂停的SocialLoginsend_notification_mail(*args, **kwargs)转交 account 适配器发送通知邮件如账号断开通知get_requests_session()构造requests.Session统一设置SOCIALACCOUNT_REQUESTS_TIMEOUT默认 5 秒超时所有对 provider 的上游 HTTP 请求generate_state_param(state)返回随机字符串长度由statekit.STATE_ID_LENGTH决定生成 OAuth state 参数测试中常被覆写用于调试/追踪generate_state_param的默认实现使用 Django 的get_random_stringallauth/socialaccount/adapter.py。state 参数的机制说明可参考 allauth/socialaccount/internal/statekit.pystate 只是实际状态的引用指针因此默认用随机串即可。十一、适配器在登录全流程中的位置综合源码调用链一次典型社交登录中适配器方法的执行顺序如下用户点击登录 → provider 完成 OAuth 握手complete_login()allauth/socialaccount/internal/flows/login.py→pre_social_login()get_adapter().pre_social_login(request, sociallogin)可抛ImmediateHttpResponse中止发送pre_social_login信号根据process分流login / connect / redirect登录新用户 →process_signup()allauth/socialaccount/internal/flows/signup.py→is_open_for_signup()→is_auto_signup_allowed()→ 自动注册或save_user()/get_signup_form_initial_data()→complete_social_signup()连接已有用户 →do_connect()allauth/socialaccount/internal/flows/connect.py→get_connect_redirect_url()认证失败 →on_authentication_error()。这解释了为什么绝大多数定制需求只需覆写适配器方法而不必触碰视图层。十二、实战定义一个完整的自定义适配器综合上述所有方法一个覆盖主要定制点的自定义适配器示例# myapp/adapters.py from django.core.exceptions import ImmediateHttpResponse from django.shortcuts import redirect from allauth.socialaccount.adapter import DefaultSocialAccountAdapter class MySocialAccountAdapter(DefaultSocialAccountAdapter): def pre_social_login(self, request, sociallogin): # 新用户首次登录前强制走一次欢迎页 if not sociallogin.is_existing: raise ImmediateHttpResponse(redirect(/welcome/)) def on_authentication_error(self, request, provider, errorNone, exceptionNone, extra_contextNone): # 认证失败时记录日志自定义业务 pass def is_auto_signup_allowed(self, request, sociallogin): # 只对已验证邮箱的 provider 数据允许自动注册 return sociallogin.email_addresses and all( e.verified for e in sociallogin.email_addresses ) def get_signup_form_initial_data(self, sociallogin): data super().get_signup_form_initial_data(sociallogin) data[username] data[username] or fuser_{sociallogin.account.uid} return data def get_connect_redirect_url(self, request, socialaccount): return /accounts/social/connections/ def is_email_verified(self, provider, email): # 对内部企业邮箱一律视为已验证 if email.endswith(example.com): return True return super().is_email_verified(provider, email) def generate_state_param(self, state): import uuid return fmyapp-{uuid.uuid4().hex}然后在 Django 设置中启用SOCIALACCOUNT_ADAPTER myapp.adapters.MySocialAccountAdapter十三、相关设置速查与适配器行为紧密相关的SOCIALACCOUNT_*设置完整列表见 docs/socialaccount/configuration.rst读取逻辑见 allauth/socialaccount/app_settings.py设置默认值影响的方法SOCIALACCOUNT_ADAPTERallauth.socialaccount.adapter.DefaultSocialAccountAdapter整个适配器SOCIALACCOUNT_AUTO_SIGNUPTrueis_auto_signup_allowedSOCIALACCOUNT_EMAIL_AUTHENTICATIONFalsecan_authenticate_by_emailSOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECTFalse邮箱认证后是否自动绑定账号SOCIALACCOUNT_EMAIL_REQUIREDemail* in ACCOUNT_SIGNUP_FIELDS注册是否需要邮箱SOCIALACCOUNT_EMAIL_VERIFICATIONACCOUNT_EMAIL_VERIFICATION社交账号的邮箱验证策略SOCIALACCOUNT_PROVIDERS{}list_apps/is_email_verified等SOCIALACCOUNT_REQUESTS_TIMEOUT5get_requests_sessionSOCIALACCOUNT_STORE_TOKENSFalsetoken 存储SOCIALACCOUNT_ONLYFalse仅社交登录模式结语DefaultSocialAccountAdapter是 django-allauth 社交登录能力的定制中枢从登录前拦截、注册开关、用户字段填充、应用发现、邮箱验证到连接跳转与断开校验几乎所有可定制点都以方法形式暴露。掌握了SOCIALACCOUNT_ADAPTER的配置方式与每个方法的默认语义、调用时机和底层调用链登录流程、注册流程、连接流程你就可以在不修改任何框架代码的前提下将第三方登录流程精确调校到符合自身业务需求。赞分享后端认证鉴权身份认证【免费下载链接】django-allauthIntegrated 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/项目地址https://gitcode.com/gh_mirrors/dj/django-allauth点击查看免费下载相关推荐django-allauth 集成 Dropbox 社交登录从 App 注册到 OAuth2 回调的完整配置指南django allauth 集成 Dropbox 社交登录从 App 注册到 OAuth2 回调的完整配置指南 本指南面向 Django 开发者系统讲解如后端认证鉴权身份认证django-allauth 集成微博WeiboOAuth2 社交登录回调地址限制与完整配置指南django allauth 集成微博WeiboOAuth2 社交登录回调地址限制与完整配置指南 导读 本文聚焦 django allauth 内置的微博后端认证鉴权身份认证n8n Node Configuration 实战指南面向 Agent 的操作感知节点配置与属性依赖解析n8n Node Configuration 实战指南面向 Agent 的操作感知节点配置与属性依赖解析 导读 本指南围绕 n8n mcp 项目中 n8n n后端认证鉴权身份认证上一篇如何用猫抓cat-catch实现高效资源捕获从入门到专家的实战指南下一篇网易云音乐FLAC无损下载终极方案从音质提升到音乐库构建全流程指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表