49

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

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

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

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

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

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

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

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

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

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

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

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

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

4

5 回答 5

42

注意(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)

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

于 2015-06-02T09:10:10.390 回答
28

您将需要覆盖 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`

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

于 2013-10-18T06:37:12.357 回答
14

根据对此相关线程的babus评论,在此之前发布的建议答案(12)引入了一个很大的安全漏洞,记录在 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'))
于 2016-02-04T15:25:45.857 回答
3

我刚刚在源代码中找到了这条评论:

        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.

所以,不可能按设计来做。

于 2013-10-16T02:01:33.233 回答
1

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

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

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

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

快乐的场景应该是:

  1. 用户通过您网站上的电子邮件和密码创建帐户。用户已注销。
  2. 用户忘记了他的帐户并尝试通过他的 Facebook 登录。
  3. 系统通过 Facebook 验证用户并发现他已经通过其他方法创建了帐户(电子邮件相同)。系统将用户重定向到正常登录页面,并显示消息“您已经使用电子邮件和密码创建了帐户。请以这种方式登录。登录后,您将能够使用 Facebook 并登录。”
  4. 用户通过电子邮件和密码登录。
  5. 系统自动将他的 Facebook 登录名与他的帐户连接起来。下次用户可以使用 Facebook 登录或电子邮件和密码。
于 2015-10-11T08:58:53.060 回答