如何在Django REST框架中使用JWT认证(附实例)

606 阅读4分钟

JWT是JSON Web Token的缩写,它是客户端/服务器应用程序使用的一种认证策略,其中客户端是使用JavaScript和一些前端框架(如Angular、React或VueJS)的Web应用程序。

在本教程中,我们将探讨JWT认证的具体内容。如果你想了解更多关于使用Django REST框架(DRF)的基于令牌的认证,或者你想知道如何开始一个新的DRF项目,你可以阅读本教程。这些概念都是一样的,我们只是要换一个认证后端。

JWT如何工作?

JWT只是一个授权令牌,应包含在所有请求中:

curl http://127.0.0.1:8000/hello/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQzODI4NDMxLCJqdGkiOiI3ZjU5OTdiNzE1MGQ0NjU3OWRjMmI0OTE2NzA5N2U3YiIsInVzZXJfaWQiOjF9.Ju70kdcaHKn1Qaz8H42zrOYk0Jx9kIckTn9Xx7vhikY'

JWT是通过将用户名+密码与访问令牌刷新令牌交换而获得的。

访问令牌通常是短暂的(在5分钟左右到期,但可以定制)。

刷新令牌的寿命稍长一些(24小时内到期,也可定制)。它相当于一个认证会话。过期后,你需要再次用用户名+密码完全登录。

这是为什么呢?

这是一个安全功能,也是因为JWT拥有更多的信息。如果你仔细看我上面给出的例子,你会看到令牌由三个部分组成。

xxxxx.yyyyy.zzzzz

这是组成JWT的三个独特的部分:

header.payload.signature

所以我们这里有:

header = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload = eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQzODI4NDMxLCJqdGkiOiI3ZjU5OTdiNzE1MGQ0NjU3OWRjMmI0OTE2NzA5N2U3YiIsInVzZXJfaWQiOjF9
signature = Ju70kdcaHKn1Qaz8H42zrOYk0Jx9kIckTn9Xx7vhikY

这些信息是用Base64编码的。如果我们进行解码,我们会看到这样的东西:

头部

{
  "typ": "JWT",
  "alg": "HS256"
}

有效载荷

{
  "token_type": "access",
  "exp": 1543828431,
  "jti": "7f5997b7150d46579dc2b49167097e7b",
  "user_id": 1
}

签名

签名是由JWT后端发出的,使用头base64 + payload base64 +SECRET_KEY 。在每次请求时,这个签名都会被验证。如果头或有效载荷中的任何信息被客户端改变,它将使签名无效。检查和验证签名的唯一方法是通过使用你的应用程序的SECRET_KEY 。在其他方面,这就是为什么你应该始终保持你的SECRET_KEY秘密


安装和设置

在本教程中,我们将使用 djangorestframework_simplejwt 库,该库由DRF开发者推荐。

pip install djangorestframework_simplejwt

设置.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

urls.py

from django.urls import path
from rest_framework_simplejwt import views as jwt_views

urlpatterns = [
    # Your URLs...
    path('api/token/', jwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
]

示例代码

在本教程中,我将使用以下路由和API视图:

views.py

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


class HelloView(APIView):
    permission_classes = (IsAuthenticated,)

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

urls.py

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

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

使用方法

我将使用HTTPie来通过终端消费API端点。但你也可以使用cURL(在许多操作系统中都可以使用)在本地进行尝试。

或者使用DRF的Web界面,像这样访问端点的URL:

DRF JWT Obtain Token

获取令牌

第一步是认证和获取令牌。该端点是/api/token/ ,它只接受POST请求:

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

HTTPie JWT Obtain Token

所以基本上你的响应体就是两个令牌:

{
    "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQ1MjI0MjU5LCJqdGkiOiIyYmQ1NjI3MmIzYjI0YjNmOGI1MjJlNThjMzdjMTdlMSIsInVzZXJfaWQiOjF9.D92tTuVi_YcNkJtiLGHtcn6tBcxLCBxz9FKD3qzhUg8",
    "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTU0NTMxMDM1OSwianRpIjoiMjk2ZDc1ZDA3Nzc2NDE0ZjkxYjhiOTY4MzI4NGRmOTUiLCJ1c2VyX2lkIjoxfQ.rA-mnGRg71NEW_ga0sJoaMODS5ABjE5HnxJDb0F8xAo"
}

之后,你要在客户端存储访问令牌刷新令牌,通常是在localStorage中。

为了访问后端受保护的视图(即需要认证的API端点),你应该在所有请求的头中包含访问令牌,像这样:

http http://127.0.0.1:8000/hello/ "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQ1MjI0MjAwLCJqdGkiOiJlMGQxZDY2MjE5ODc0ZTY3OWY0NjM0ZWU2NTQ2YTIwMCIsInVzZXJfaWQiOjF9.9eHat3CvRQYnb5EdcgYFzUyMobXzxlAVh_IAgqyvzCE"

HTTPie JWT Hello, World!

你可以在未来5分钟内使用这个访问令牌

五分钟后,该令牌将过期,如果你试图再次访问该视图,你将会得到以下错误。

http http://127.0.0.1:8000/hello/ "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQ1MjI0MjAwLCJqdGkiOiJlMGQxZDY2MjE5ODc0ZTY3OWY0NjM0ZWU2NTQ2YTIwMCIsInVzZXJfaWQiOjF9.9eHat3CvRQYnb5EdcgYFzUyMobXzxlAVh_IAgqyvzCE"

HTTPie JWT Expired

刷新令牌

要获得一个新的访问令牌,你应该使用刷新令牌端点/api/token/refresh/ ,发布刷新令牌

http post http://127.0.0.1:8000/api/token/refresh/ refresh=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTU0NTMwODIyMiwianRpIjoiNzAyOGFlNjc0ZTdjNDZlMDlmMzUwYjg3MjU1NGUxODQiLCJ1c2VyX2lkIjoxfQ.Md8AO3dDrQBvWYWeZsd_A1J39z6b6HEwWIUZ7ilOiPE

HTTPie JWT Refresh Token

返回的是一个新的访问令牌,你应该在随后的请求中使用。

刷新令牌在未来24小时内有效。当它最终也过期时,用户将需要使用他们的用户名和密码再次进行完整的认证,以获得一套新的访问令牌+刷新令牌


刷新令牌的意义何在?

乍一看,刷新令牌可能看起来毫无意义,但事实上它是必要的,以确保用户仍然有正确的权限。如果你的访问令牌有很长的过期时间,更新与令牌相关的信息可能需要更长时间。这是因为认证检查是通过加密手段完成的,而不是通过查询数据库和验证数据完成的。所以有些信息是被缓存的。

还有一个安全方面,从某种意义上说,刷新令牌只在POST数据中旅行。而访问令牌是通过HTTP头发送的,可能会在途中被记录下来。因此,这也给了一个短暂的窗口,如果你的访问令牌被破坏。