如何使用Django REST框架实现令牌认证(完整教程)

465 阅读4分钟

在本教程中,你将学习如何使用Django REST框架(DRF)实现基于令牌的认证。 令牌认证的工作原理是用用户名和密码交换一个令牌,该令牌将被用于所有后续请求,以便在服务器端识别用户。

在客户端如何处理认证的具体细节取决于你所使用的技术/语言/框架的不同。客户端可以是一个使用iOS或Android的移动应用程序,可以是一个使用Python或C++的桌面应用程序。它可能是一个使用PHP或Ruby的Web应用程序。

但一旦你了解了整个过程,就会更容易为你的特定用例找到必要的资源和文档。

令牌认证适用于客户端-服务器应用程序,在那里令牌被安全地存储。你不应该暴露你的令牌,因为这将(某种程度上)等同于把你的用户名和密码交给别人。

设置REST API项目

那么,让我们从最开始的地方开始吧。安装Django和DRF:

pip install django
pip install djangorestframework

创建一个新的Django项目:

django-admin.py startproject myapi .

导航到myapi文件夹:

cd myapi

启动一个新的应用程序。我将把我的应用程序称为core

django-admin.py startapp core

以下是你的项目结构应该是这样的:

myapi/
 |-- core/
 |    |-- migrations/
 |    |-- __init__.py
 |    |-- admin.py
 |    |-- apps.py
 |    |-- models.py
 |    |-- tests.py
 |    +-- views.py
 |-- __init__.py
 |-- settings.py
 |-- urls.py
 +-- wsgi.py
manage.py

core应用(你创建的)和rest_framework应用(你安装的)添加到INSTALLED_APPS ,在settings.py模块内。

myapi/settings.py

INSTALLED_APPS = [
    # Django Apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-Party Apps
    'rest_framework',

    # Local Apps (Your project's apps)
    'myapi.core',
]

回到项目根目录(即manage.py脚本所在的文件夹),并迁移数据库:

python manage.py migrate

让我们创建我们的第一个API视图,只是为了测试一下:

myapi/core/views.py

from rest_framework.views import APIView
from rest_framework.response import Response

class HelloView(APIView):
    def get(self, request):
        content = {'message': 'Hello, World!'}
        return Response(content)

现在在urls.py模块中注册一个路径:

myapi/urls.py

from django.urls import path
from myapi.core import views

urlpatterns = [
    path('hello/', views.HelloView.as_view(), name='hello'),
]

所以现在我们有一个只有一个端点的API/hello/ ,我们可以执行GET 请求。我们可以使用浏览器来消费这个端点,只要访问URLhttp://127.0.0.1:8000/hello/

Hello Endpoint HTML

我们也可以通过在querystring中传递format 参数来要求接收纯JSON数据的响应,如http://127.0.0.1:8000/hello/?format=json

Hello Endpoint JSON

这两种方法都可以用来尝试DRF API,但有时命令行工具更方便,因为我们可以更容易地玩弄请求头文件。你可以使用cURL,它在所有主要的Linux/MacOS发行版上都广泛使用:

curl http://127.0.0.1:8000/hello/

Hello Endpoint cURL

但通常我更喜欢使用HTTPie,它是一个相当棒的Python命令行工具:

http http://127.0.0.1:8000/hello/

Hello Endpoint HTTPie

现在我们来保护这个API端点,这样我们就可以实现令牌认证:

myapi/core/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated  # <-- Here


class HelloView(APIView):
    permission_classes = (IsAuthenticated,)             # <-- And here

    def get(self, request):
        content = {'message': 'Hello, World!'}
        return Response(content)

再试着访问这个API端点:

http http://127.0.0.1:8000/hello/

Hello Endpoint HTTPie Forbidden

现在我们得到一个HTTP 403 Forbidden错误。现在我们来实现令牌认证,这样我们就可以访问这个端点了。


实现令牌认证

我们需要在我们的settings.py模块中添加两个信息。首先将rest_framework.authtoken加入你的INSTALLED_APPS ,并将TokenAuthentication 加入到REST_FRAMEWORK

myapi/settings.py

INSTALLED_APPS = [
    # Django Apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-Party Apps
    'rest_framework',
    'rest_framework.authtoken',  # <-- Here

    # Local Apps (Your project's apps)
    'myapi.core',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # <-- And here
    ],
}

迁移数据库,创建将存储认证令牌的表:

python manage.py migrate

Migrate Auth Token

现在我们需要一个用户账户。让我们使用manage.py 命令行工具创建一个:

python manage.py createsuperuser --username vitor --email vitor@example.com

最简单的方法是再次使用命令行工具来生成一个令牌,只是为了测试目的:

python manage.py drf_create_token vitor

drf_create_token

这条信息,即随机字符串9054f7aa9305e012b3c2300408c3dfdf390fcddf ,是我们接下来要用来验证的。

但现在我们已经有了TokenAuthentication ,让我们试着再向我们的/hello/ 端点发出请求:

http http://127.0.0.1:8000/hello/

WWW-Authenticate Token

注意我们的API现在是如何向客户提供一些所需的认证方法的额外信息的。

所以,最后,让我们使用我们的令牌吧!

http http://127.0.0.1:8000/hello/ 'Authorization: Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf'

REST Token Authentication

这就差不多了。从现在开始,在所有的后续请求中,你应该包括头信息Authorization: Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf

这个格式看起来很奇怪,而且通常是一个关于如何设置这个头的混乱点。这将取决于客户端和如何设置HTTP请求头。

例如,如果我们使用cURL,命令会是这样的:

curl http://127.0.0.1:8000/hello/ -H 'Authorization: Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf'

或者如果是Python请求的调用:

import requests

url = 'http://127.0.0.1:8000/hello/'
headers = {'Authorization': 'Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf'}
r = requests.get(url, headers=headers)

或者如果我们使用的是Angular,你可以实现一个HttpInterceptor ,并设置一个头:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const user = JSON.parse(localStorage.getItem('user'));
    if (user && user.token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Token ${user.accessToken}`
        }
      });
    }
    return next.handle(request);
  }
}

用户请求一个令牌

DRF提供了一个端点,让用户使用他们的用户名和密码来请求认证令牌。

urls.py模块中包含以下路由:

myapi/urls.py

from django.urls import path
from rest_framework.authtoken.views import obtain_auth_token  # <-- Here
from myapi.core import views

urlpatterns = [
    path('hello/', views.HelloView.as_view(), name='hello'),
    path('api-token-auth/', obtain_auth_token, name='api_token_auth'),  # <-- And here
]

因此,现在我们有了一个全新的API端点,它是/api-token-auth/ 。让我们先检查一下它:

http http://127.0.0.1:8000/api-token-auth/

API Token Auth

它并不处理GET请求。基本上它只是一个接收带有用户名和密码的POST请求的视图。

让我们再试一下。

http post http://127.0.0.1:8000/api-token-auth/ username=vitor password=123

API Token Auth POST

响应体是与这个特定用户相关的令牌。在这一点上,你存储这个令牌,并将其应用到未来的请求中。

然后,同样,你要向API发出POST请求的方式取决于你所使用的语言/框架。

如果这是一个Angular客户端,你可以将令牌存储在localStorage ,如果这是一个桌面CLI应用程序,你可以将其存储在用户主目录下的一个文本文件中的dot文件。


结论

希望本教程提供了一些关于令牌认证如何工作的见解。我将尝试跟进本教程,提供一些Angular应用程序、命令行应用程序和Web客户端的具体例子。

需要注意的是,默认的令牌实现有一些限制,比如每个用户只能有一个令牌,没有内置的方法来设置令牌的到期日。