如何使用任何 url 上的查询参数对用户进行身份验证?

How can I authenticate a user with a query parameter on any url?

假设用户登陆 https://example.com/any/page?token=hhdo28h3do782

使用查询字符串对用户进行身份验证和登录的推荐方法是什么?

我正在考虑创建调用 authenticate() 的某种包罗万象的视图(我也想知道如何执行此操作 :D)。然后我会建立一个自定义后端来验证用户。

这是实现我想要的理想方式吗?

干杯!

我假设您正在项目中使用 Django REST Framework and also enabled the TokenAuthentication 机制。如果是这样,请继续,

from rest_framework.authentication import TokenAuthentication


class QueryParamAuthentication(TokenAuthentication):
    query_param_name = 'token'

    def authenticate(self, request):
        token = request.query_params.get(self.query_param_name)
        if token:
            return self.authenticate_credentials(token)
        return None

然后,将 DRF DEFAULT_AUTHENTICATION_CLASSES 更改为

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'dotted.path.to.QueryParamAuthentication'
    ),
    # rest of your DRF settings...

}

更新

要在没有 DRF 的情况下执行此操作,您必须编写自定义模型后端(这是一个有点冗长的主题)

参考: Writing an authentication backend

所以,从管理您的代币开始。这是一个基本模型:

class Token(models.Model):
    code = models.CharField(max_length=255)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    expires = models.DateTimeField()

可以生成自定义身份验证后端来检查令牌的有效性:

class TokenAuthenticationBackend(ModelBackend):
    def authenticate(self, request, token=None):
        try:
            token = Token.objects.get(code=token, expires__gte=now())
        except Token.DoesNotExist:
            return None
        else:
            return token.user

如果您使用的是基于 class 的视图,您可以编写一个 mixin 来检查令牌是否存在,然后执行您的身份验证逻辑:

class UrlTokenAuthenticationMixin:
    def dispatch(self, request, *args, **kwargs):
        if 'token' in request.GET:
            user = authenticate(request, request.GET['token'])
            if user:
                login(request, user)
        return super(UrlTokenAuthenticationMixin, self).dispatch(request, *args, **kwargs)

要在给定视图上使用它,只需按如下方式声明您的视图:

class MyView(UrlTokenAuthenticationMixin, TemplateView):
    # view code here

例如

将此实现为一揽子包罗万象的另一种方法是使用中间件而不是混合:

class TokenAuthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if 'token' in request.GET:
            user = authenticate(request, request.GET['token'])
            if user:
                login(request, user)
        return self.get_response(request)

为此,您需要 create a custom authentication backend 验证 api 键。

在此示例中,request 自动检查有效令牌。您根本不需要修改您的观点。这是因为它包含对用户进行身份验证的自定义中间件。

为简洁起见,我假设有效的用户令牌存储在一个模型中,该模型是外键到 django auth.User 模型。

# my_project/authentication_backends.py
from django.contrib import auth
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.contrib.auth.middleware import AuthenticationMiddleware

TOKEN_QUERY_PARAM = "token"

class TokenMiddleware(AuthenticationMiddleware):
    def process_request(self, request):
        try:
            token = request.GET[TOKEN_QUERY_PARAM]
        except KeyError:
            # A token isn't included in the query params
            return

        if request.user.is_authenticated:
            # Here you can check that the authenticated user has the same `token` value
            # as the one in the request. Otherwise, logout the already authenticated
            # user.
            if request.user.token.key == token:
                return
            else:
                auth.logout(request)

        user = auth.authenticate(request, token=token)
        if user:
            # The token is valid. Save the user to the request and session.
            request.user = user
            auth.login(request, user)

class TokenBackend(ModelBackend):
    def authenticate(self, request, token=None):
        if not token:
            return None

        try:
            return User.objects.get(token__key=token)
        except User.DoesNotExist:
            # A user with that token does not exist
            return None

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

现在,除了您可能已经拥有的任何现有后端或中间件之外,您还可以在 settings.py 中添加 AUTHENTICATION_BACKENDSMIDDLEWARE 的路径。如果您使用的是默认值,它将如下所示:

MIDDLEWARE = [
    # ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",

    # This is the dotted path to your backend class. For this example,
    # I'm pretending that the class is in the file:
    #     my_project/authentication_backends.py
    "my_project.authentication_backends.TokenMiddleware",

    # ...
]

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "my_project.authentication_backends.TokenBackend",
]