手动登录用户而不需要密码

80

我希望你能帮我找出一种最佳方式来实现手动(由服务器发起)登录,而不使用密码。让我解释一下工作流程:

  • 用户注册
  • 谢谢!已发送包含激活链接的电子邮件等等
  • (账户现在存在但标记为未启用)
  • 用户打开电子邮件,点击链接
  • (账户已启用)
  • 谢谢!您现在可以使用该网站了

我的目标是在用户点击电子邮件链接后立即登录他,以便他可以立即开始使用网站。

由于密码在数据库中被加密,所以我不能使用他的密码,那么唯一的选择是编写自定义身份验证后端吗?


请参考以下线程:https://dev59.com/_Gw15IYBdhLWcg3whME1#55217797 - Sameer Kumar Choudhary
在 DRF 下进行测试时,请查看 force_authenticate 函数APIClient.force_authenticate 方法 - phoenix
5个回答

104

登录用户时不需要密码。只需使用auth.login函数,该函数接受一个User对象,您预计已经从数据库获取了该对象并启用了该帐户。因此,您可以直接将该对象传递给login

当然,你需要非常小心,确保没有办法欺骗用户链接到一个现有的已启用的帐户,并自动将他们登录为该用户。

from django.contrib.auth import login

def activate_account(request, hash):
    account = get_account_from_hash(hash)
    if not account.is_active:
        account.activate()
        account.save()
        user = account.user
        login(request, user)

编辑:

嗯,没有注意到必须使用 authenticate,因为它会添加额外的属性。看着代码,它所做的只是一个 backend 属性,相当于认证后端的模块路径。所以你可以假装它 - 在上面的登录调用之前,做这个:

user.backend = 'django.contrib.auth.backends.ModelBackend'

1
谢谢,文档也同意,但是还有这个警告: “首先调用authenticate() 当您手动登录用户时,必须在调用login()之前调用authenticate()。 authenticate()设置了一个属性,指示哪个身份验证后端成功地对该用户进行了身份验证(有关详细信息,请参阅后端文档),并且在登录过程中稍后需要此信息。” 这可能会成为问题吗? - Agos
虽然最好从django.contrib.conf导入设置,并在使用自定义后端的情况下分配settings.AUTHENTICATION_BACKENDS。 - Arsham
这个不起作用,即使修改过;你必须创建一个Auth后端并添加到你的设置中;否则它将无法在页面加载之间保持用户登录状态。 - warath-coder
@warath-coder 你说得对。我按照Daniel所说的做了,但是在多次ajax请求之后,django服务器响应将cookies会话重置为null,因此用户必须重新登录。如何解决这个问题? - Jcyrss
1
从Django 1.6开始,似乎需要将backend设置实际上列在AUTHENTICATION_BACKENDS列表中才能正常工作。 - Tim Tisdall
从Django 1.10开始,这是内置的。请参见Ian Clark在下面的评论:https://dev59.com/iHE85IYBdhLWcg3waCxT#39364110 - eupharis

30

自Django 1.10起,该过程已经简化。

在所有版本的Django中,为了使用户登录,他们必须通过应用程序的一个后端进行身份验证(由AUTHENTICATION_BACKENDS设置控制)。

如果您只想强制登录,可以声称用户已通过该列表中的第一个后端进行了身份验证:

from django.conf import settings
from django.contrib.auth import login


# Django 1.10+
login(request, user, backend=settings.AUTHENTICATION_BACKENDS[0])

# Django <1.10 -  fake the authenticate() call
user.backend = settings.AUTHENTICATION_BACKENDS[0]
login(request, user)

1
不错!有个小笔误。你把设置写了两遍。应该是 login(request, user, backend=settings.AUTHENTICATION_BACKENDS[0]) - eupharis
@ eupharis,谢谢!已经修复好了! - Ian Clark
1
这是一个非常好的答案,适用于2021年的Django 3.2。 - thms

27

Daniel的回答非常好。

另一种方法是创建一个 HashModelBackend,按照自定义授权后端的方式 https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#writing-an-authentication-backend 进行操作,如下所示:

class HashModelBackend(object):
    def authenticate(self, hash=None):
        user = get_user_from_hash(hash)
        return user

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

然后在您的设置中安装这个:

AUTHENTICATION_BACKENDS = (
    'myproject.backends.HashModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

那么你的视图将类似于这样:

def activate_account(request, hash):
    user = authenticate(hash=hash)
    if user:
        # check if user is_active, and any other checks
        login(request, user)
    else:
        return user_not_found_bad_hash_message

它完美地工作。以下是一些可能对某人有帮助的注释:我使用activate_account作为在django的SessionMiddlewareAuthenticationMiddleware之后加载的中间件。然后我在django的ModelBackend之前使用了我的cakend。如果有人需要一个完整的示例,请告诉我,我会创建一个新的答案。 - mrroot5
@mrroot5,您能否发布您的解决方案?特别是为什么这里需要一个中间件? - Neil
抱歉@Neil,但这个评论是关于2个月前的事情了,那时候我的代码已经改变了,我记不得例子了。如果我找到了2个月前的提交,我会发布例子的。 - mrroot5

3

回复的答案。

编写后端的一种方法:

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

class HashModelBackend(ModelBackend):

def authenticate(self, username=None, **kwargs):
    UserModel = get_user_model()
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)
    try:
        user = UserModel._default_manager.get_by_natural_key(username)
        return user
    except UserModel.DoesNotExist:
        return None

本答案基于django.contrib.auth.backends.ModelBackend源代码。适用于Django 1.9。

我更倾向于将自定义后端放在Django默认后端之后:

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'yours.HashModelBackend',
]

由于激活账户比登录本身更不可能。根据https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#specifying-authentication-backends的说明:

AUTHENTICATION_BACKENDS 的顺序很重要,因此如果相同的用户名和密码在多个后端中有效,则 Django 将停止处理第一个正面匹配。

请注意,即使密码不正确,此代码也会对您的用户进行身份验证。

这段代码存在语法错误,因此不清楚应该发生什么('hash'已定义但未使用,'username'已使用但未定义)。 - Amichai Schreiber

2
您可以使用ska包,它已经实现了无需密码登录Django。 ska使用身份验证令牌,并且其安全性基于SHARED_KEY,对于涉及的所有方(服务器)都应该是相等的。
在客户端(请求无密码登录的一方),您可以使用ska生成并签署一个URL。例如:
from ska import sign_url
from ska.contrib.django.ska.settings import SECRET_KEY

server_ska_login_url = 'https://server-url.com/ska/login/'

signed_url = sign_url(
    auth_user='test_ska_user_0',
    secret_key=SECRET_KEY,
    url=server_ska_login_url
    extra={
        'email': 'john.doe@mail.example.com',
        'first_name': 'John',
        'last_name': 'Doe',
    }
)

令牌的默认生存期为 600 秒。您可以通过提供 lifetime 参数来自定义该值。

在服务器端(用户登录的站点)中,假设您已经正确安装了 ska,当用户访问 URL 并且如果他们存在(用户名匹配),则将其登录;否则,将创建新用户。在您项目的 Django 设置中,有三个回调函数可供自定义。

  • USER_GET_CALLBACK(字符串):如果成功从数据库中获取用户,则触发此函数(现有用户)。
  • USER_CREATE_CALLBACK(字符串):在用户创建后立即触发此函数(用户不存在)。
  • USER_INFO_CALLBACK(字符串):在身份验证成功时触发此函数。

详见文档 (http://pythonhosted.org/ska/)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接