Django allauth 社交登录:使用注册的电子邮件自动链接社交网站配置文件

Posted

技术标签:

【中文标题】Django allauth 社交登录:使用注册的电子邮件自动链接社交网站配置文件【英文标题】:Django allauth social login: automatically linking social site profiles using the registered email 【发布时间】:2013-10-21 15:37:34 【问题描述】:

我的目标是为我的 Django 网站的用户创造最简单的登录体验。我想像:

    登录屏幕显示给用户 用户选择使用 Facebook 或 Google 登录 用户在外部站点输入密码 用户可以作为经过身份验证的用户与我的网站进行交互

好的,这部分很简单,只需要安装django-allauth并配置即可。

但我也想提供与本地用户一起使用该站点的选项。它会有另一个步骤:

    登录屏幕显示给用户 用户选择注册 用户输入凭据 网站发送验证电子邮件 用户点击电子邮件链接并可以作为经过身份验证的用户与我的网站进行交互

好的,默认认证和allauth都可以。但现在是百万美元的问题。

如果他们改变了登录方式,我如何自动关联他们的 Google、FB 和本地帐户?

看到他们以任何方式登录,我都有他们的电子邮件地址。是否可以使用 django-allauth 来做到这一点?我知道我可以用user intervention 做到这一点。今天的默认行为是拒绝登录,说电子邮件已经注册。

如果无法仅使用配置进行操作,我将接受这个答案,该答案为我提供了一些关于我应该在 allauth 代码中进行哪些修改以支持此工作流的方向。

这样做的原因有很多。用户会忘记他们使用哪种方法进行身份验证,有时会使用 Google,有时会使用 FB,有时会使用本地用户帐户。我们已经有很多本地用户帐户,社交帐户将成为一项新功能。我希望用户保持他们的身份。我设想可以询问用户朋友列表,所以如果他们使用谷歌登录,我也想拥有他们的 FB 帐户。

这是一个业余爱好网站,没有很高的安全要求,所以请不要回答说这不是一个明智的安全实施。

稍后,我将创建一个 custom user model 以仅将电子邮件作为登录 ID。但我会很高兴得到一个答案,让我自动关联具有所需用户名的默认用户模型的帐户。

我正在使用 Django==1.5.4 和 django-allauth==0.13.0

【问题讨论】:

django-social-auth 得到了你正在寻找的东西。您可以将您的授权逻辑放在您设置中的社交身份验证管道中的任何位置。我对 all-auth 一无所知,但 social-auth 并不难安装和开始使用,但它有很多后端并且非常灵活 @PukeCloud:我没有设法让 django-social-auth 工作。他们正在将他们的项目转移到另一个名为 python-social-auth 的包中,文档确实令人困惑。我已经让 allauth 开箱即用。在网上阅读,其他人也提到 allauth 更容易使用。看起来 allauth 正在获得动力。我会再等一会儿,看看是否有人在这里回答。 Allauth 支持将多个社交帐户链接到一个用户,因此您只需使用 allauth 即可完成上述所有操作。 @elssar:当然,我知道它支持它。它有一个非常好的模型来支持它。我想知道如何自动完成。 啊,现在我明白了。如果已经注册的用户尝试使用与她注册时相同的电子邮件使用其他社交登录,您希望将他们登录到已经存在的帐户。好的,我很确定这是可能的。将查看 allauth 的代码并回复您。 【参考方案1】:

注意 (2018-10-23):我不再使用这个了。发生了太多的魔术。相反,我启用了SOCIALACCOUNT_EMAIL_REQUIRED'facebook': 'VERIFIED_EMAIL': False, ... 。因此,allauth 将重定向社交注册表单上的社交登录以输入有效的电子邮件地址。如果它已经注册,则会出现错误,请先登录,然后再连接帐户。对我来说足够公平。


我正在尝试改进这种用例并提出以下解决方案:

from allauth.account.models import EmailAddress
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class SocialAccountAdapter(DefaultSocialAccountAdapter):
    def pre_social_login(self, request, sociallogin):
        """
        Invoked just after a user successfully authenticates via a
        social provider, but before the login is actually processed
        (and before the pre_social_login signal is emitted).

        We're trying to solve different use cases:
        - social account already exists, just go on
        - social account has no email or email is unknown, just go on
        - social account's email exists, link social account to existing user
        """

        # Ignore existing social accounts, just do this stuff for new ones
        if sociallogin.is_existing:
            return

        # some social logins don't have an email address, e.g. facebook accounts
        # with mobile numbers only, but allauth takes care of this case so just
        # ignore it
        if 'email' not in sociallogin.account.extra_data:
            return

        # check if given email address already exists.
        # Note: __iexact is used to ignore cases
        try:
            email = sociallogin.account.extra_data['email'].lower()
            email_address = EmailAddress.objects.get(email__iexact=email)

        # if it does not, let allauth take care of this new social account
        except EmailAddress.DoesNotExist:
            return

        # if it does, connect this new social login to the existing user
        user = email_address.user
        sociallogin.connect(request, user)

据我测试,它似乎运行良好。但是非常欢迎输入和建议!

【讨论】:

这比接受的答案好得多。我正在使用它并可能集成一些修改;如果我提出任何好的改进,我会再发表评论。 这确实比预期的答案更好,但是我认为您已经假设电子邮件地址已经过验证,可以假设吗? 其实不是,见 Victor Grau Serrat 的回答。例如,Facebook 不清楚是否每个电子邮件地址都经过验证。 我的解决方案基于上述内容,但对我有用的是,除了 EmailAddress.DoesNotExist 之外,您在该行之后返回:我创建了它丢失的电子邮件地址,然后它工作正常。 谢谢大家!它工作正常!我使用 Google API 进行了测试。【参考方案2】:

您将需要覆盖 sociallogin 适配器,特别是 pre_social_login 方法,该方法在与社交提供者进行身份验证后、allauth 处理此登录之前调用。

my_adapter.py,做这样的事情

from django.contrib.auth.models import User

from allauth.account.models import EmailAccount
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter


class MyAdapter(DefaultSocialAccountAdapter):
    def pre_social_login(self, request, sociallogin):
        # This isn't tested, but should work
        try:
            user = User.objects.get(email=sociallogin.email)
            sociallogin.connect(request, user)
            # Create a response object
            raise ImmediateHttpResponse(response)
        except User.DoesNotExist:
            pass

然后在您的设置中,将社交适配器更改为您的适配器

SOCIALACCOUNT_ADAPTER = 'myapp.my_adapter.MyAdapter`

您应该能够以这种方式将多个社交帐户连接到一个用户。

【讨论】:

像沙姆一样工作!谢谢!但为我获取用户的正确方法是:user = User.objects.get(email=sociallogin.account.user.email) 实际上没有得到这个工作。除了import EmailAccount 上的一些导入错误和上面的评论,它在assert not self.is_existing 上失败github.com/pennersr/django-allauth/blob/master/allauth/… 顺便说一句,帐户正在连接。你能提供完整的工作示例吗?谢谢。 如果您尝试连接已保存的社交帐户,则会引发断言错误。所以只需在之前添加一个 if 条件检查,看看 socialaccount 是否有 pk。如果有返回 raise ImmediateHttpResponse(response) 中的response 应该来自哪里? @nhinkle 您必须自己创建响应对象,就像评论中所说的那样。【参考方案3】:

根据this related thread 上的 babus 评论,在此之前发布的建议答案(1、2)引入了一个很大的安全漏洞,记录在 allauth 文档中:

“从 Facebook 文档中并不清楚帐户经过验证这一事实是否意味着电子邮件地址也经过验证。例如,也可以通过电话或信用卡进行验证。要为安全起见,默认设置是将来自 Facebook 的电子邮件地址视为未经验证。”

这么说,我可以使用您的电子邮件 ID 在 facebook 上注册,或者在 facebook 中将我的电子邮件更改为您的电子邮件,然后登录该网站以访问您的帐户。

因此考虑到这一点,并在@sspross 答案的基础上,我的方法是将用户重定向到登录页面,并通知她/他重复,并邀请他使用她/他的其他帐户登录,并在他们登录后链接它们。我承认这与原始问题不同,但这样做不会引入安全漏洞。

因此,我的适配器看起来像:

from django.contrib.auth.models import User
from allauth.account.models import EmailAddress
from allauth.exceptions import ImmediateHttpResponse
from django.shortcuts import redirect
from django.contrib import messages
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class MyAdapter(DefaultSocialAccountAdapter):
    def pre_social_login(self, request, sociallogin):
        """
        Invoked just after a user successfully authenticates via a
        social provider, but before the login is actually processed
        (and before the pre_social_login signal is emitted).

        We're trying to solve different use cases:
        - social account already exists, just go on
        - social account has no email or email is unknown, just go on
        - social account's email exists, link social account to existing user
        """

        # Ignore existing social accounts, just do this stuff for new ones
        if sociallogin.is_existing:
            return

        # some social logins don't have an email address, e.g. facebook accounts
        # with mobile numbers only, but allauth takes care of this case so just
        # ignore it
        if 'email' not in sociallogin.account.extra_data:
            return

        # check if given email address already exists.
        # Note: __iexact is used to ignore cases
        try:
            email = sociallogin.account.extra_data['email'].lower()
            email_address = EmailAddress.objects.get(email__iexact=email)

        # if it does not, let allauth take care of this new social account
        except EmailAddress.DoesNotExist:
            return

        # if it does, bounce back to the login page
        account = User.objects.get(email=email).socialaccount_set.first()
        messages.error(request, "A "+account.provider.capitalize()+" account already exists associated to "+email_address.email+". Log in with that instead, and connect your "+sociallogin.account.provider.capitalize()+" account through your profile page to link them together.")       
        raise ImmediateHttpResponse(redirect('/accounts/login'))

【讨论】:

***.com/questions/14280535/… 如果你真的想“将社交帐户链接到现有用户”,你必须用 sociallogin.connect(request, email_address.user) 替换“如果是,则返回登录页面”部分 我已经读到如果 FB 确实返回了一封电子邮件,它保证会被验证,所以真的不再存在安全问题了。 @EvanZamir 是的,我还读到 FB 现在保证只返回经过验证的电子邮件。【参考方案4】:

我刚刚在源代码中发现了这个注释:

        if account_settings.UNIQUE_EMAIL:
            if email_address_exists(email):
                # Oops, another user already has this address.  We
                # cannot simply connect this social account to the
                # existing user. Reason is that the email adress may
                # not be verified, meaning, the user may be a hacker
                # that has added your email address to his account in
                # the hope that you fall in his trap.  We cannot check
                # on 'email_address.verified' either, because
                # 'email_address' is not guaranteed to be verified.

所以,设计是不可能的。

【讨论】:

有什么可以规避的钩子或技巧吗?我被困在这里:/【参考方案5】:

如果他们改变了登录方式,我如何自动关联他们的 Google、FB 和本地帐户?

这是可能的,但您必须小心安全问题。检查场景:

    用户通过电子邮件和密码在您的网站上创建帐户。用户没有 Facebook。 攻击者使用用户电子邮件在 Facebook 上创建帐户。 (假设场景,但您无法控制社交网络是否验证电子邮件)。 攻击者使用 Facebook 登录您的网站并自动获取用户原始帐户的访问权限。

但是你可以修复它。我描述解决票https://github.com/pennersr/django-allauth/issues/1149

快乐的场景应该是:

    用户通过电子邮件和密码在您的网站上创建帐户。用户已退出。 用户忘记了他的帐户并尝试通过他的 Facebook 登录。 系统通过Facebook验证用户,发现他已经通过其他方法创建了帐户(电子邮件相同)。系统将用户重定向到正常登录页面,并显示消息“您已经使用电子邮件和密码创建了帐户。请以这种方式登录。登录后,您将能够使用 Facebook 并登录。” 用户通过电子邮件和密码登录。 系统自动将他的 Facebook 登录名与他的帐户关联起来。下次用户可以使用 Facebook 登录或电子邮件和密码。

【讨论】:

但 Facebook 会要求电子邮件验证,不是吗?

以上是关于Django allauth 社交登录:使用注册的电子邮件自动链接社交网站配置文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 django-allauth 和 django-rest 框架

django-rest-auth + django-allauth + angular2

django-allauth:将多个社交帐户链接到单个用户

Django从零搭建个人博客 | 使用allauth插件管理用户登录与注册

Django 注册和登录 - 举例说明

使用 django-allauth 登录信号将用户重定向到另一个 url