正在进入ing...

Django rest_framework 源码学习笔记(三)鉴权相关

发布时间:2022-04-13 浏览量: 699 文章分类: python

关于django DRF的鉴权方式其实比较灵活,你可以采用自己引入jwt,手动来控制,当然你也可以引入第三方库djangorestframework-jwt。相对比之前,我更推荐使用djangorestframework-jwt原因也很简单,出活快,功能全。

运行环境

djangorestframework          3.12.4
djangorestframework-jwt      1.11.0
django-cors-headers          3.11.0
django-filter                21.1
django-taggit                2.0.0
Markdown                     3.3.6
Django                       3.2.9

rest_framework_jwt.views的方法

rest_framework_jwt.views提供了三个对外的方法,分别是obtain_jwt_tokenrefresh_jwt_tokenverify_jwt_token

obtain_jwt_token登陆

这个比较简单,只要使用post方法,传递usernamepassword两个参数就可以了。

在请求完毕后,服务器会反馈类似下面的信息。

POST /obtain_jwt_token/
HTTP 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9.eZbFu1532eIHW8OCUAn5qC2q-JaFdxntdaF2iUbygCQ"
}

对上面的jwt进行base64转码

#     eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9.eZbFu1532eIHW8OCUAn5qC2q-JaFdxntdaF2iUbygCQ
# 加密方式说明
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 = {"typ":"JWT","alg":"HS256"}
# jwt body
eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9 = {"user_id":1,"username":"admin","exp":1649821924,"email":""}

refresh_jwt_token刷新Token

使用post传递给他一个有效期内的token,他会在返回一个新的给到你。类似延长续期。

Verify Json Web Token校验Token

使用post传递给他一个有效期内的token,他会验证是否正确。

补充说明

关于CSRF的补充问题,可以查阅 djangoCSRF的使用和禁止说明-佩恩的博客

上手

demo1

先手动实现一个最基础的,

这个时候如果我通过127.0.0.1/dog/则返回我{ "detail": "用户认证失败" },而如果是访问http://127.0.0.1:8000/dog/?token=123就会返回{ "code": 20000, "msg": "OK" }. 实现了基本的函数前置拦截验证

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

class Myauthentication(object):
    # 自己实现的认证类
    def authenticate_header(self,val):
        pass
    def authenticate(self,request):
        token = request._request.GET.get('token')
        if not token:
            raise exceptions.AuthenticationFailed('用户认证失败')
        return ('admin',None)


class DogView(APIView):
    authentication_classes = [Myauthentication,]
    def get(self,request,*args,**kwargs):
        ret = {
            'code': 20000,
            'msg':'OK'
        }
        return Response(ret)

demo2

在请求中完善以下4点。

  • 认证

认证分为局部使用全局使用,rest_framework本身也提供了内置的认证类rest_framework.authentication(都是基于Django本身认证实现)。 - BaseAuthentication - BasicAuthentication - SessionAuthentication - TokenAuthentication - RemoteUserAuthentication

  # 局部使用,在view增加 authentication_classes = 
  class Myauthentication(BaseAuthentication):
    # 自己实现的认证类
    def authenticate(self,request):
        token = request._request.GET.get('token')
        if not token:
            raise exceptions.AuthenticationFailed('用户认证失败')
        return ('admin',None)

  class DogView(APIView):
    authentication_classes = [Myauthentication,]
    def get(self,request,*args,**kwargs):
        ret = {
            'code': 20000,
            'msg':'OK'
        }
        return Response(ret)

  #全局使用,需要在settings 添加。api.utils.auth.Myauthentication,字符串路径
  REST_FRAMEWORK = {
      # 全局使用的认证类
      "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
      "UNAUTHENTICATED_USER":None, # 匿名 request.user = None
      "UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
  }
  • 权限 权限同样分为局部使用全局使用rest_framework本身也提供了内置的认证类rest_framework.permissions(都是基于Django本身权限实现)。
  • BasePermissionMetaclass
  • AllowAny
  • IsAuthenticated
  • IsAdminUser
  • IsAuthenticatedOrReadOnly
  • DjangoModelPermissions
  from rest_framework.permissions import BasePermission# 局部使用,自己可以实现各种验证规则
  class MyPermission(BasePermission):
    message = "无权访问" # 自定义无权时候返回的内容
    def has_permission(self,request,view):
        return False


  class DogView(APIView):
    # 权限
    permission_classes = [MyPermission,]
    def get(self,request,*args,**kwargs):
        ret = {
            'code': 20000,
            'msg':'OK'
        }
        return Response(ret)
  # 返回的是{
  #    "detail": "You do not have permission to perform this action."
  #}


  # 全局使用 DEFAULT_PERMISSION_CLASSES
  REST_FRAMEWORK = {
      # 全局使用的认证类
      "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
      "UNAUTHENTICATED_USER":None, # 匿名 request.user = None
      "UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
      "DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
  }
  • 节流 节流同样分为局部使用全局使用rest_framework本身也提供了内置的认证类rest_framework.throttling
  • BaseThrottle
  • SimpleRateThrottle
  • AnonRateThrottle
  • UserRateThrottle
  • ScopedRateThrottle
  # 局部使用
  from rest_framework.throttling import SimpleRateThrottle

  class VisitThrottle(SimpleRateThrottle):
    # 标识
    scope = 'guest'
    def get_cache_key(self, request, view):
        # 以IP来作为判断条件
        return self.get_ident(request)

  class DogView(APIView):
    throttle_classes = [VisitThrottle,]
    def get(self,request,*args,**kwargs):
        ret = {
            'code': 20000,
            'msg':'OK'
        }
        return Response(ret)

  # 全局使用 DEFAULT_THROTTLE_RATES
  REST_FRAMEWORK = {
      # 全局使用的认证类
      "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
      "UNAUTHENTICATED_USER":None, # 匿名 request.user = None
      "UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
      "DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
      "DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
      # 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
      "DEFAULT_THROTTLE_RATES":{
          "guest":'3/m'
      }
  • 版本

版本同样分为局部使用全局使用rest_framework本身也提供了内置的认证类rest_framework.versioning。 - BaseVersioning - AcceptHeaderVersioning - URLPathVersioning - NamespaceVersioning - HostNameVersioning - QueryParameterVersioning

# 局部使用
from rest_framework.versioning import BaseVersioning

class ParamVersion(BaseVersioning):
    def determine_version(self,request,*args,**kwargs):
        version = request.query_params.get('version')
        return version

class DogView(APIView):
    versioning_class = ParamVersion
    def get(self,request,*args,**kwargs):
        ret = {
            'code': 20000,
            'msg':'OK'
        }
        print('request.version:',request.version)
        return Response(ret)


# 全局使用
REST_FRAMEWORK = {
    # 全局使用的认证类
    "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
    "UNAUTHENTICATED_USER":None, # 匿名 request.user = None
    "UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
    "DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
    "DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
    # 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
    "DEFAULT_THROTTLE_RATES":{
        "endpain":'3/m',
    },
    # 版本配置相关
    "DEFAULT_VERSION":"v1",
    "ALLOWED_VERSIONS":['1.0','1.1'],
    "VERSION_PARAM":"version"
}
# 函数中可以通过 request.version 获取实际版本

但是实际我们用url传递参数的方式日常使用中还是比较少的。更多还是通过路径来获取

url = http://127.0.0.1:8000/myviews/?version=v1  # url参数传参
url = http://127.0.0.1:8000/v1/myviews/

类似上面这种设置也是比较简单的,可以直接做全局设置

    # 版本配置相关
REST_FRAMEWORK = {
    # 全局使用的认证类
    "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
    "UNAUTHENTICATED_USER":None, # 匿名 request.user = None
    "UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
    "DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
    "DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
    # 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
    "DEFAULT_THROTTLE_RATES":{
        "endpain":'30/m',
    },
    # 版本配置相关
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    "DEFAULT_VERSION":"v1",
    "ALLOWED_VERSIONS":['1.0','1.1'],
    "VERSION_PARAM":"version"
}

# 同时也要注意URL需要修改设置
# urls.py
from django.urls import path,re_path
from api.views import views

urlpatterns = [
    re_path(r'^(?P<version>[v1|v2]+)/dog/$', views.DogView.as_view()),
]  

#个人认为比较叼的功能就是,直接反向传递
   re_path(r'^(?P<version>[v1|v2]+)/dog/$', views.DogView.as_view(),name='dog'),

    request.versioning_scheme.reverse(
        viewname='dog',
        request=request
    )  

结合jwt一起使用鉴权案例

没有实现自己的验证规则,基本就是配置一下,实现接口登陆访问,当然如果有需要自己权限等认证方式的话,相信看完上面的内容,你肯定也会自己在添加了。

# Settings.py
# REST_FRAMEWORK配置
REST_FRAMEWORK = {
    # 全局认证器
    "DEFAULT_AUTHENTICATION_CLASSES":[
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ],
    # 使用JWT进行权限认证
    "DEFAULT_PERMISSION_CLASSES":['rest_framework.permissions.IsAuthenticated'],
    # 全局序列化器
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
}

import datetime
# JWT设置
JWT_AUTH = {
    # Token编码方法
    'JWT_ENCODE_HANDLER':
        'rest_framework_jwt.utils.jwt_encode_handler',
    # Token解码方法
    'JWT_DECODE_HANDLER':
        'rest_framework_jwt.utils.jwt_decode_handler',

    'JWT_SECRET_KEY': settings.SECRET_KEY,
    'JWT_GET_USER_SECRET_KEY': None,
    'JWT_PUBLIC_KEY': None,
    'JWT_PRIVATE_KEY': None,
    # 算法
    'JWT_ALGORITHM': 'HS256',
    # 开启验证
    'JWT_VERIFY': True,
    # 开启验证过期时间
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    # 一天后Token过期
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUDIENCE': None,
    'JWT_ISSUER': None,
    # 开启Token更新
    'JWT_ALLOW_REFRESH': True,
    # 一天后刷新的Token过期
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
    # 默认Token前缀
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_AUTH_COOKIE': None,
}

按照这个配置,然后在实际页面请求的时候按照下面的样子即可。

jwt = 'xxxxx.xxxxxxxxxx.xxxxxx'
url = 'http://127.0.0.1:8000/getdata/'
headers = {
    "Authorization":f"JWT {jwt}"
}
requests.get(url,headers=headers)
req = requests.get()
req.json()