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

Django allauth社交登录:使用已注册的电子邮件自动链接社交网站配置文件,django,django-1.5,django-allauth,Django,Django 1.5,Django Allauth,我的目标是为我的Django网站的用户创造一种最简单的登录体验。我想象的是: 登录屏幕显示给用户 用户选择使用Facebook或Google登录 用户在外部站点中输入密码 用户可以作为经过身份验证的用户与我的站点交互 好的,这部分很简单,只需安装和配置它 但我也希望能让本地用户使用该网站。这将有另一个步骤: 登录屏幕显示给用户 用户选择注册 用户输入凭据 网站发送验证电子邮件 用户单击电子邮件链接,可以作为经过身份验证的用户与我的网站进行交互 好的,默认身份验证和allauth都可以做到。但现在

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

  • 登录屏幕显示给用户
  • 用户选择使用Facebook或Google登录
  • 用户在外部站点中输入密码
  • 用户可以作为经过身份验证的用户与我的站点交互
  • 好的,这部分很简单,只需安装和配置它

    但我也希望能让本地用户使用该网站。这将有另一个步骤:

  • 登录屏幕显示给用户
  • 用户选择注册
  • 用户输入凭据
  • 网站发送验证电子邮件
  • 用户单击电子邮件链接,可以作为经过身份验证的用户与我的网站进行交互
  • 好的,默认身份验证和allauth都可以做到。但现在是百万美元的问题

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

    无论他们以何种方式登录,我都有他们的电子邮件地址。可以用django allauth来做吗?我知道我可以用它。今天的默认行为是拒绝登录,说电子邮件已经注册

    如果仅仅通过配置是不可能的,我会接受这个答案,它会给我一些指导,告诉我应该在allauth代码中做哪些修改来支持这个工作流

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

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

    稍后,我会创建一个以电子邮件作为登录id的帐户。但是我很乐意得到一个答案,它可以让我自动关联一个默认用户模型的帐户,该帐户具有所需的用户名


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

    我刚刚在源代码中找到了这条注释:

            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.
    

    所以,设计是不可能的

    您需要覆盖SocialIn适配器,特别是方法,该方法在与社交提供商进行身份验证后调用,但在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`
    
    您应该能够通过这种方式将多个社交帐户连接到一个用户。

    注意(2018-10-23):我不再使用它了。太多的魔法正在发生。相反,我启用了
    SOCIALACCOUNT\u EMAIL\u REQUIRED
    'facebook':{'VERIFIED\u 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)
    
    就我所能测试的而言,它似乎工作得很好。但我们非常欢迎您的意见和建议

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

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

  • 用户通过电子邮件和密码在您的网站上创建帐户。用户没有Facebook
  • 攻击者使用用户电子邮件在Facebook上创建帐户。(假设情况,但您无法控制社交网络是否验证电子邮件)
  • 攻击者使用Facebook登录到您的站点,并自动访问用户的原始帐户
  • 但是你可以解决它。我描述了解决问题的方法

    令人高兴的情景应该是:

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

  • 根据babus的评论,在本次(,)之前发布的建议答案引入了一个巨大的安全漏洞,记录在allauth文档中:

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

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

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

    因此,我的适配器如下所示:

    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'))
    

    django social auth正是你想要的东西。您可以将授权逻辑放在