飞道的博客

Django REST Framework教程(7): 如何使用JWT认证(神文多图)

444人阅读  评论(0)

在前面的DRF系列文章中,我们介绍了DRF认证(authentication)的本质, 以及自带的几种认证方案,包括TokenAuthentication方案。然而JSON Web Token(JWT)是一种更新的使用token进行身份认证的标准。与DRF内置的TokenAuthentication方案不同,JWT身份验证不需要使用数据库来验证令牌, 而且可以轻松设置token失效期或刷新token, 是API开发中当前最流行的跨域认证解决方案。本文将详细介绍JWT认证的工作原理以及如何通过djangorestframework-simplejwt 这个第三方包轻松实现JWT认证。如果有一篇文章我敢拍胸脯说,你今天可能用不到,但总有一天你会需要回过头来阅读它并使用它,那么我指的就是本文,强烈建议先收藏再阅读。

其实网上已经有不少关于JWT的文章,大同小异。为了避免重复造轮子,本文将以原创翻译国外medium.com上的一篇神文为主 (原作:Yunus Emre Cevik)并辅以自己的解读,希望对你有所帮助。

什么是Json Web Token及其工作原理

JSON Web Token(JWT)是一种开放标准,它定义了一种紧凑且自包含的方式,用于各方之间安全地将信息以JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。JWT用于为应用程序创建访问token,通常适用于API身份验证和服务器到服务器的授权。那么如何理解紧凑和自包含这两个词的含义呢?

  • 紧凑:就是说这个数据量比较少,可以通过url参数,http请求提交的数据以及http header多种方式来传递。

  • 自包含:这个字符串可以包含很多信息,比如用户id,用户名,订单号id等,如果其他人拿到该信息,就可以拿到关键业务信息。

那么JWT认证是如何工作的呢? 首先客户端提交用户登录信息验证身份通过后,服务器生成一个用于证明用户身份的令牌(token),也就是一个加密后的长字符串,并将其发送给客户端。在后续请求中,客户端以各种方式(比如通过url参数或者请求头)将这个令牌发送回服务器,服务器就知道请求来自哪个特定身份的用户了。

JSON Web Token由三部分组成,这些部分由点(.)分隔,分别是header(头部),payload(有效负载)和signature(签名)。

  • header(头部): 识别以何种算法来生成签名;

  • pyload(有效负载): 用来存放实际需要传递的数据;

  • signature(签名): 安全验证token有效性,防止数据被篡改。

通过http传输的数据实际上是加密后的JWT,它是由两个点分割的base64-URL长字符串组成,解密后我们可以得到header, payload和signature三部分。我们可以简单的使用 https://jwt.io/ 官网来生成或解析一个JWT,如下所示:

接下来我们将使用django-rest-framework-simplejwt这个第三方软件包进行JWT身份验证。

Django中如何使用JWT认证

django-rest-framework-simplejwt为Django REST框架提供了JSON Web令牌认证后端。它提供一组保守的默认功能来涵盖了JWT的最常见用例。它还非常容易扩展。

首先,我们要使用pip安装它。

pip install djangorestframework-simplejwt

其次,我们需要告诉DRF我们使用jwt认证作为后台认证方案。修改myproject/settings.py:

#myproject/settings.py


   
  1. REST_FRAMEWORK = {
  2. 'DEFAULT_FILTER_BACKENDS': [
  3. 'django_filters.rest_framework.DjangoFilterBackend'
  4. ],
  5. 'DEFAULT_AUTHENTICATION_CLASSES': [
  6. 'rest_framework_simplejwt.authentication.JWTAuthentication',
  7. ],
  8. }

接下来,我们需要提供用户可以获取和刷新token的urls地址,这两个urls分别对应TokenObtainPairView和TokenRefreshView两个视图。


   
  1. from django.contrib import admin
  2. from django.urls import path, include
  3. from reviews.views import ProductViewSet, ImageViewSet
  4. from rest_framework.routers import DefaultRouter
  5. from django.conf import settings
  6. from django.conf.urls.static import static
  7. from rest_framework_simplejwt.views import (
  8. TokenObtainPairView,
  9. TokenRefreshView,
  10. )
  11. router = DefaultRouter()
  12. router.register( r'product', ProductViewSet, basename= 'Product')
  13. router.register( r'image', ImageViewSet, basename= 'Image')
  14. urlpatterns = [
  15. path( 'admin/', admin.site.urls),
  16. path( 'token/', TokenObtainPairView.as_view(), name= 'token_obtain_pair'),
  17. path( 'token/refresh/', TokenRefreshView.as_view(), name= 'token_refresh'),
  18. path( '', include(router.urls)),
  19. ]
  20. if settings.DEBUG:
  21. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

最后,我们可以开始使用postman测试了。通过POST方法发送登录请求到/token/, 请求数据包括username和password。如果登录成功,你将得到两个长字符串,一个是access token(访问令牌),还有一个是refresh token(刷新令牌),如下所示:

假如你有一个受保护的视图(比如这里的/image/),权限(permission_classes)是IsAuthenticated,只有验证用户才可访问。访问这个保护视图时你只需要在请求头的Authorization选项里输入你刚才获取的access token即可,如下所示:

不给这个access token默认只有5分钟有效。5分钟过后,当你再次访问保护视图时,你将得到如下token已失效或过期的提示:

去获取新的access token,你需要使用之前获得的refresh token。你将这个refresh token放到请求的正文(body)里,发送POST请求到/token/refresh/即可获得刷新后的access token(访问令牌), 如下所示:

那么问题来了,Simple JWT中的access token默认有效期是5分钟,那么refresh token默认有效期是多长呢? 答案是24小时。

更改Simple JWT的默认设置

Simple JWT的默认设置如下所示:


   
  1. DEFAULTS = {
  2. 'ACCESS_TOKEN_LIFETIME': timedelta(minutes= 5),
  3. 'REFRESH_TOKEN_LIFETIME': timedelta(days= 1),
  4. 'ROTATE_REFRESH_TOKENS': False,
  5. 'BLACKLIST_AFTER_ROTATION': True,
  6. 'ALGORITHM': 'HS256',
  7. 'SIGNING_KEY': settings.SECRET_KEY,
  8. 'VERIFYING_KEY': None,
  9. 'AUDIENCE': None,
  10. 'ISSUER': None,
  11. 'AUTH_HEADER_TYPES': ( 'Bearer',),
  12. 'USER_ID_FIELD': 'id',
  13. 'USER_ID_CLAIM': 'user_id',
  14. 'AUTH_TOKEN_CLASSES': ( 'rest_framework_simplejwt.tokens.AccessToken',),
  15. 'TOKEN_TYPE_CLAIM': 'token_type',
  16. 'JTI_CLAIM': 'jti',
  17. 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
  18. 'SLIDING_TOKEN_LIFETIME': timedelta(minutes= 5),
  19. 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days= 1),
  20. }

如果要覆盖Simple JWT的默认设置,可以修改settings.py, 如下所示。下例将refresh token的有效期改为了15天。


   
  1. from datetime import timedelta
  2. SIMPLE_JWT = {
  3. 'REFRESH_TOKEN_LIFETIME': timedelta(days= 15),
  4. 'ROTATE_REFRESH_TOKENS': True,
  5. }

自定义令牌(token)

如果你对Simple JWT返回的access token进行解码,你会发现这个token的payload数据部分包括token类型,token失效时间,jti(一个类似随机字符串)和user_id。如果你希望在payload部分提供更多信息,比如用户的username,这时你就要自定义令牌(token)了。

首先,编写你的myapp/seralizers.py,添加如下代码。该序列化器继承了TokenObtainPairSerializer类。


   
  1. from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
  2. class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
  3. @classmethod
  4. def get_token(cls, user):
  5. token = super(MyTokenObtainPairSerializer, cls).get_token(user)
  6. # Add custom claims
  7. token[ 'username'] = user.username
  8. return token

其次,不使用Simple JWT提供的默认视图,使用自定义视图。修改myapp/views.py, 添加如下代码:


   
  1. from rest_framework_simplejwt.views import TokenObtainPairView
  2. from rest_framework.permissions import AllowAny
  3. from .serializers import MyTokenObtainPairSerializer
  4. class MyObtainTokenPairView(TokenObtainPairView):
  5. permission_classes = (AllowAny,)
  6. serializer_class = MyTokenObtainPairSerializer

最后,修改myproject/urls.py, 添加如下代码,将/token/指向新的自定义的视图。注意:本例中的app名为reviews,所以是从reviews.views导入的MyObtainTokenPairView。


   
  1. from django.contrib import admin
  2. from django.urls import path, include
  3. from reviews.views  import ProductViewSet, ImageViewSet, MyObtainTokenPairView
  4. from rest_framework.routers import DefaultRouter
  5. from django.conf import settings
  6. from django.conf.urls.static import static
  7. from rest_framework_simplejwt.views import TokenRefreshView
  8. router = DefaultRouter()
  9. router.register( r'product', ProductViewSet, basename= 'Product')
  10. router.register( r'image', ImageViewSet, basename= 'Image')
  11. urlpatterns = [
  12. path( 'admin/', admin.site.urls),
  13. path( 'token/', MyObtainTokenPairView.as_view(), name= 'token_obtain_pair'),
  14. path( 'token/refresh/', TokenRefreshView.as_view(), name= 'token_refresh'),
  15. path( '', include(router.urls)),
  16. ]
  17. if settings.DEBUG:
  18. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

重新发送POST请求到/token/,你将获得新的access token和refresh token,如下所示:

对重新获取的access token进行解码,你将看到payload部分多了username的内容,是不是很酷? 在实际API开发过程中,通过Json Web Token传递更多数据非常有用。

自定义认证后台(Backend)

上面的演示案例是通过用户名和密码登录的,如果我们希望后台同时支持邮箱/密码,手机/密码组合登录怎么办? 这时我们还需要自定义认证后台(Backend)。

首先,修改users/views.py, 添加如下代码:


   
  1. from django.contrib.auth.backends import ModelBackend
  2. from django.db.models import Q
  3. from django.contrib.auth import get_user_model
  4. User = get_user_model()
  5. class MyCustomBackend(ModelBackend):
  6.      def authenticate(self, request, username=None, password=None, **kwargs):
  7.          try:
  8. user = User.objects.get(Q(username=username) | Q(email=username) )
  9. if user.check_password(password):
  10. return user
  11. except Exception as e:
  12. return None

其次,修改myproject/settings.py, 把你自定义的验证方式添加到AUTHENTICATION_BACKENDS里去。


   
  1. AUTHENTICATION_BACKENDS = (
  2. 'users.views.MyCustomBackend',
  3. )

修改好后,你使用postman发送邮箱/密码组合到/token/,将同样可以获得access token和refresh token。是不是又学到了一招?

小结

本文讲解与演示的内容非常多,介绍了什么是JWT及其工作原理,演示了如何使用Simple JWT这个第三方安装包,如何自定义令牌(token)和认证后台(backend)。到此对DRF的认证与权限部分我们就介绍完了,接下来我们将分别介绍分页、过滤、限流和解析器,欢迎关注我们的微信公众号【Python Web与Django开发】。

大江狗

2020.11.7

参考原文

https://medium.com/django-rest/django-rest-framework-jwt-authentication-94bee36f2af8


转载:https://blog.csdn.net/weixin_42134789/article/details/109554843
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场