自定义 django 身份验证后端不会在第一次登录用户,但在第二次工作

Custom django authentication backend doesn't log user in first time, but works second time

所以我使用 Rdio 来登录和创建用户,并编写了一个后端来处理它的 oauth。第一次尝试使用 Rdio 登录时,它会创建一个用户和一个附加的 Rdio 用户,但不会创建会话和 return 会话 cookie。

该流程就像任何 oauth2 流程一样:您在我的应用程序上按下一个按钮,它将 w/get 参数重定向到 Rdio,然后 Rdio 在我的应用程序上调用回调视图(以及 GET 参数中的代码)。在那个回调视图中,我调用 authenticate:

class RdioCallbackView(View):
def get(self, request):
    """ here, you need to create and auth a django user and create and tie the rdio user's stuff to it """
    if request.user.is_authenticated() == False:
        try:
            rdio_code = request.GET['code']
        except KeyError:
            return redirect(reverse('login'))
        # authenticate
        user = auth.authenticate(rdio_code=rdio_code)
        if user is not None and user.is_active:
            auth.login(request, user)
        else:
            return render(request, 'home/login.html', {'rdio_url': create_rdio_auth_url(), 'message': "That code didn't seem to work"})
    else:
        # user exists!
        user = request.user
    return HttpResponseRedirect(reverse('the-next-view'))

自定义身份验证后端如下所示:

class RdioBackend(object):
def authenticate(self, rdio_code=None):
    token_info = exchange_rdio_code(rdio_code)
    try:
        access_token = token_info['access_token']
        refresh_token = token_info['refresh_token']
    except KeyError:
        return None
    except TypeError:
        # the code was probably already used.
        return None

    rdio_user_dict = get_rdio_user_for_access_token(access_token)
    rdio_key = rdio_user_dict['key']

    try:
        rdio_user = RdioUser.objects.get(rdio_id=rdio_key)
        rdio_user.access_token = access_token
        rdio_user.refresh_token = refresh_token
        rdio_user.save()
        user = rdio_user.user
    except RdioUser.DoesNotExist:
        user = User.objects.create(username=rdio_key)
        user.set_unusable_password()
        rdio_user = RdioUser.objects.create(
            rdio_id = rdio_key,
            access_token = access_token,
            refresh_token = token_info['refresh_token'],
            user = user,
            )
    return user

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

这就是事情变得奇怪的地方。它似乎没有创建一个新的 Session 对象,而且绝对没有 return 一个会话 cookie。但是,当我返回并再次进行 Rdio 登录时,它 return 是一个会话 cookie,使会话在后端进行,并且登录和身份验证工作正常。

而且我认为我的 AUTHENTICATION_BACKENDS 设置是正确的:

AUTHENTICATION_BACKENDS = (
'appname.backend.RdioBackend',
'django.contrib.auth.backends.ModelBackend',
)

编辑:更多可能相关的信息: 它重定向到具有 LoginRequiredMixin 的视图:

class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
    view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
    return login_required(view)

并且在 RdioCallbackView 中,当我将最后一行从 return HttpResponseRedirect(reverse('the-next-view')) 更改为直接使用 return render(request, 'path/to.html', param_dict) 提供模板时,它确实提供了 cookie 并创建了一个 sessionid,但随后将其删除从数据库和浏览器离开该屏幕的那一刻。

这可能是有史以来最愚蠢的错误。事实证明,如果你创建一个没有密码的用户,你不需要调用user.set_unusable_password()。如果你确实调用 user.set_unusable_password(),它会以某种方式混淆你所做的任何身份验证(即使在你调用之后)。

所以为了解决这个问题,我刚刚在我的自定义 django 身份验证后端中删除了对 user.set_unusable_password() 的调用。