Django Rest Framework 生命周期,认证鉴权

1 什么是drf?

在python项目开发中,前后端分离的技术框架越来越成熟,在前后端进行通信时,通常需要用统一的格式进行通信,目前应用比较广泛的是RESTful API。那后端如何快速编写基于Django的RESTful API呢?本篇将主要介绍使用DjangoRestFramework(drf)框架来快速开发符合REST风格的API。

2 为什么用drf?

提供了可视化的API调试界面,开发者可以在线测试接口

可以根据需求来选择常规视图功能或更高级的功能

不用自己写大量的CRUD接口了,简单配置即可

支持ORM(对象映射关系)和非ORM的数据序列化

# 安装drfpip install  djangorestframework# 具体功能在具体模块下from rest_framework.request import Requestfrom rest_framework.response import Responsefrom rest_framework.exceptions import APIExceptionfrom rest_framework.filters import OrderingFilterfrom rest_framework.views import APIViewfrom rest_framework.pagination import PageNumberPaginationfrom rest_framework.settings import APISettings# 注册drfappINSTALLED_APPS = [    ...    'rest_framework',]复制代码

as_view: 就干了一件事,禁用csrf认证

三大认证任务分析

**认证模块:**校验用户是是否登陆

self.perform_authentication(request)复制代码

**权限模块:**校验用户是否拥有权限

self.check_permissionsn(request)复制代码

**节流模块:**访问接口的次数在设定的时间范围内是否过快(配置访问频率、缓存计次、超次后需要等待的时间)

self.check_throttles(request)复制代码

3 Django请求生命周期

前端发送请求

wsgi, 他就是socket服务端,用于接收用户请求并将请求进行初次封装,然后将请求交给web框架(Flask、Django)

中间件处理请求,帮助我们对请求进行校验或在请求对象中添加其他相关数据,例如:csrf、request.session

路由匹配,根据当前请求的URL找到视图函数,如果是FBV写法,通过判断method两类型,找到对应的视图函数;如果是CBV写法,匹配成功后会自动去找dispatch方法,然后Django会通过dispatch反射的方式找到类中对应的方法并执行

视图函数,在视图函数中进行业务逻辑的处理,可能涉及到:orm、view视图将数据渲染到template模板

视图函数执行完毕之后,会把客户端想要的数据返回给dispatch方法,由dispatch方法把数据返回经客户端

中间件处理响应

wsgi,将响应的内容发送给浏览器

浏览器渲染

4 drf请求生命周期

前端发送请求 --> Django的wsgi --> 中间件 --> 路由系统_执行CBV的as_view(),就是执行内部的dispath方法 --> 在执行dispath之前,有版本分析和渲染器 --> 在dispath内,对request封装 --> 版本 --> 认证 --> 权限 --> 限流 --> 通过反射执行视图函数 --> 如果视图用到缓存( request.data or request.query_params )就用到了 解析器 --> 视图处理数据,用到了序列化(对数据进行序列化或验证) --> 视图返回数据可以用到分页

认证流程


Django认证与三种方法,此处使用第三种自定义方法

方法一 系统session认证

rest_framework.authentication.SessionAuthenticationajax请求通过认证:cookie中要携带 sessionid、csrftoken,请求头中要携带 x-csrftoken复制代码

方法二 jwt认证

rest_framework_jwt.authentication.JSONWebTokenAuthenticationajax请求通过认证:请求头中要携带 authorization,值为 jwt空格token复制代码

方法三(常用)自定义:基于jwt、其它

1)自定义认证类,继承BaseAuthentication(或其子类),重写authenticate2)authenticate中完成    拿到认证标识 auth    反解析出用户 user    前两步操作失败 返回None => 游客    前两步操作成功 返回user,auth => 登录用户    注:如果在某个分支抛出异常,直接定义失败 => 非法用户复制代码

1 流程

1.1 源码内容

当用户进行登录的时候,运行了登录类的as_view()方法,进入了APIView类的dispatch方法

执行self.initialize_request这个方法,里面封装了request和认证对象列表等其他参数

执行self.initial方法中的self.perform_authentication,里面运行了user方法

再执行了user方法里面的self._authenticate()方法

1.2 自定义方法

然后执行了自己定义的类中的authenticate方法,自己定义的类继承了BaseAuthentication类,里面有authenticate方法,如果自己定义的类中没有authenticate方法会报错

把从authenticate方法得到的user和auth赋值给user和auth方法

这两个方法把user和auth的值分别赋值给request.user:是登录用户的对象;request.auth:是认证的信息字典

2 返回值

没有携带认证信息,直接返回 None => 游客

有认证信息,校验成功,返回一个元组,第一个参数赋值给request.user,第二个赋值给request.auth

有认证信息,校验失败,抛异常 => 非法用户

3 源码介绍

# APIView的dispatch中有个self.initialize_request,它返回了一个Request类,

    它封装了django的request和认证对象列表等其他参数

class APIView(View):

    # 1.进入了APIView类的dispatch方法

    def dispatch(self, request, *args, **kwargs):

        self.args = args

        self.kwargs = kwargs

        # 2.执行了self.initialize_request这个方法,封装了request和认证对象列表等其他参数

        request = self.initialize_request(request, *args, **kwargs)

        self.request = request

        self.headers = self.default_response_headers  # deprecate?

        try:

            # 3.执行self.initial方法中的self.perform_authentication,里面运行了user方法

            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method

            if request.method.lower() in self.http_method_names:

                handler = getattr(self, request.method.lower(),

                                  self.http_method_not_allowed)

            else:

                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)


    # 2.执行了self.initialize_request这个方法,封装了request和认证对象列表等其他参数

    '''

    def initialize_request(self, request, *args, **kwargs):

        parser_context = self.get_parser_context(request)

        return Request(

            request,

            parsers=self.get_parsers(),

            authenticators=self.get_authenticators(), # [MyAuthentication(),]

            negotiator=self.get_content_negotiator(),

            parser_context=parser_context

        )

        '''


    # 3.执行self.initial方法中的self.perform_authentication,里面运行了user方法

    '''

    def initial(self, request, *args, **kwargs):

        self.format_kwarg = self.get_format_suffix(**kwargs)

        neg = self.perform_content_negotiation(request)

        request.accepted_renderer, request.accepted_media_type = neg


        # 版本方法在这里

        version, scheme = self.determine_version(request, *args, **kwargs)

        request.version, request.versioning_scheme = version, scheme

        # 认证 权限 节流三兄弟

        self.perform_authentication(request)

        self.check_permissions(request)

        self.check_throttles(request)

        '''


    # 3.执行self.initial方法中的self.perform_authentication,里面运行了user方法

    def perform_authentication(self, request):

        request.user



    # 4、再执行了user方法里面的self._authenticate()方法   

    @property

    def user(self):

        """

        Returns the user associated with the current request, as authenticated

        by the authentication classes provided to the request.

        """

        if not hasattr(self, '_user'):

            with wrap_attributeerrors():

                self._authenticate()

        return self._user

4 使用

from rest_framework import exceptions

from rest_framework.authentication import BaseAuthentication

from rest_framework.exceptions import AuthenticationFailed

from api import models

class Authtication(BaseAuthentication):

    # 常用

    def authenticate(self,request):

        token = request._request.GET.get('token')

        token_obj = models.UserToken.objects.filter(token=token).fitst()

        if not token_obj:

            raise exceptions.AuthenticationFailed('用户认证失败')


        # 在rest framework内部将会将整个两个字段赋值给request,以供后续操作使用

        return (token_obj.user, token_obj)

    # 这个方法一般用不到

    def authenticate_header(self, request):

        pass

5 引入

5.1 局部使用

from rest_framework.views import APIView

from apps.api.auth import Authtication

class UserInfoView(APIView):


    # 单独只对这个视图认证

    authentication_classes = [Authtication,]

    def get(self, request, *args, **kwargs):

        print(request.user)

        return HttpResponse('用户相关信息')

5.2 全局使用setting.py

REST_FRAMEWORK = {

    # 全局配置

    # 'DEFAULT_AUTHENTICATION_CLASSES': ['apps.api.utils.auth.FirstAuthtication', 'apps.api.utils.auth.Authtication'],

    'DEFAULT_AUTHENTICATION_CLASSES': ['apps.api.utils.auth.Authtication'],

    # 匿名,request.user = None

    'UNAUTHENTICATED_USER': None,

    # 匿名,request.auth = None

    'UNAUTHENTICATED_TOKEN': None,

}

鉴权流程


方法一:
1)AllowAny:允许所有用户,校验方法直接返回True

2)IsAuthenticated:只允许登录用户

    必须request.user和request.user.is_authenticated都通过

3)IsAuthenticatedOrReadOnly:游客只读,登录用户无限制

    get、option、head 请求无限制

    前台请求必须校验 request.user和request.user.is_authenticated

4)IsAdminUser:是否是后台用户

    校验 request.user和request.user.is_staff    is_staff(可以登录后台管理系统的用户)

方法二:

自定义:基于auth的Group与Permission表

1)自定义权限类,继承BasePermission,重写has_permission

2)has_permission中完成

    拿到登录用户 user <= request.user

    校验user的分组或是权限

    前两步操作失败 返回False => 无权限

    前两步操作成功 返回True => 有权限

1 流程

1.1 源码内容

当用户执行一个业务的时候,运行了as_view方法,进入了APIView类的dispatch方法

此处不需要封装request了,因为在认证过程中封装了

进入self.initial方法中的self.check_permissions(request)方法

里面执行了for循环,把每个权限类实例化对象

1.2 自定义方法

执行自己定义的权限类里面的has_permission方法,里面会判断request.user是否存在

不存在就返回False,存在就返回True

之后执行self.permission_denied报错方法,返回的是False就报错,可以自定义报错信息,在has_permission方法中写message = {"status": False, "error": "登录成功之后才能评论"},就实现了自定义报错

如果返回的是True就让他进入功能

2 返回值

True, 有权限,进行下一步认证(频率认证)

False, 无权限,将信息返回给前台

3 使用

重写原生方法BasePermission.has_permission

必须继承BasePermission,必须实现has_permission

utils/permission.py

from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):

    message = '必须是管理员才能访问'

    def has_permission(self, request, view):

        if request.user.user_type != 1:

            return False

        return True


models.py


from django.db import models

class UserInfo(models.Model):

    user_type_choices = [

        (1, '普通用户'),

        (2, 'VIP用户'),

        (3, 'SVIP用户'),

    ]

    username = models.CharField(max_length=32)

    password = models.CharField(max_length=32)

    user_type = models.IntegerField(choices=user_type_choices, default=0)

4 引入

4.1 全局

# settings.py

'DEFAULT_PERMISSION_CLASSES': ['apps.api.utils.permisson.MyPermission']

4.2 局部

class UserInfoView(APIView):

    """

    用户信息

    """

    permission_classes = [MyPermisson, ]

    def get(self, request, *args, **kwargs):

        print(request.user)

        return HttpResponse('用户相关信息')

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容