加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

Django rest framework----认证

发布时间:2020-12-20 12:52:53 所属栏目:Python 来源:网络整理
导读:Django rest framework----认证 ? 先了解的一些知识 理解下面两个知识点非常重要,django-rest-framework源码中到处都是基于CBV和面向对象的封装 (1)面向对象封装的两大特性 把同一类方法封装到类中将数据封装到对象中 (2)CBV CBV(class base views) ?

Django rest framework----认证

?

先了解的一些知识

理解下面两个知识点非常重要,django-rest-framework源码中到处都是基于CBV和面向对象的封装

(1)面向对象封装的两大特性

把同一类方法封装到类中

将数据封装到对象中

(2)CBV

CBV(class base views)?就是在视图里使用类处理请求。? 对应的还有?FBV(function base views)?就是在视图里使用函数处理请求。

基于反射实现根据请求方式不同,执行不同的方法

原理:url-->view方法-->dispatch方法(反射执行其它方法:GET/POST/PUT/DELETE等等)

CBV(class base views)?就是在视图里使用类处理请求。

Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以Django在后来加入了Class-Based-View。可以让我们用类写View。这样做的优点主要下面两种:

  1. 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
  2. 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性

如果我们要写一个处理GET方法的view,用函数FBV写的话是下面这样。

from django.http import HttpResponse
  
def my_view(request):
     if request.method == GET:
            return HttpResponse(OK)

如果用class-based view写的话,就是下面这样

from django.http import HttpResponse
from django.views import View
  
class MyView(View):

      def get(self,request):
            return HttpResponse(OK)

Django的url是将一个请求分配给可调用的函数的,而不是一个class。针对这个问题,class-based view提供了一个as_view()静态方法(也就是类方法),调用这个方法,会创建一个类的实例,然后通过实例调用dispatch()方法,dispatch()方法会根据request的method的不同调用相应的方法来处理request(如get()?,?post()等)。到这里,这些方法和function-based view差不多了,要接收request,得到一个response返回。如果方法没有定义,会抛出HttpResponseNotAllowed异常。

在url中,就这么写:

# urls.py
from django.conf.urls import url
from myapp.views import MyView
  
urlpatterns = [
     url(r^index/$,MyView.as_view()),]

类的属性可以通过两种方法设置,第一种是常见的Python的方法,可以被子类覆盖。

from django.http import HttpResponse
from django.views import View
  
class GreetingView(View):
    name = "yuan"
    def get(self,request):
         return HttpResponse(self.name)
  
# You can override that in a subclass
  
class MorningGreetingView(GreetingView):
    name= "alex"

?

第二种方法,你也可以在url中指定类的属性:

在url中设置类的属性Python

urlpatterns = [
   url(r^index/$,GreetingView.as_view(name="egon")),]

要理解django的class-based-view(以下简称cbv),首先要明白django引入cbv的目的是什么。在django1.3之前,generic view也就是所谓的通用视图,使用的是function-based-view(fbv),亦即基于函数的视图。有人认为fbv比cbv更pythonic,窃以为不然。python的一大重要的特性就是面向对象。而cbv更能体现python的面向对象。cbv是通过class的方式来实现视图方法的。class相对于function,更能利用多态的特定,因此更容易从宏观层面上将项目内的比较通用的功能抽象出来。关于多态,不多解释,有兴趣的同学自己Google。总之可以理解为一个东西具有多种形态(的特性)。cbv的实现原理通过看django的源码就很容易明白,大体就是由url路由到这个cbv之后,通过cbv内部的dispatch方法进行分发,将get请求分发给cbv.get方法处理,将post请求分发给cbv.post方法处理,其他方法类似。怎么利用多态呢?cbv里引入了mixin的概念。Mixin就是写好了的一些基础类,然后通过不同的Mixin组合成为最终想要的类。

所以,理解cbv的基础是,理解Mixin。Django中使用Mixin来重用代码,一个View Class可以继承多个Mixin,但是只能继承一个View(包括View的子类)

?

简单实例

settings

先创建一个project和一个app(我这里命名为api)

首先要在settings的app中添加

?

# Application definition

INSTALLED_APPS = [
    django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib.sessions,django.contrib.messages,django.contrib.staticfiles,rest_framework,api,corsheaders,# 解决跨域问题 修改1
]

urls

from django.contrib import admin
from django.urls import path
from django.conf.urls import url

from api.views import AuthView
from api.views import OrderView,UserInfoView
from api.appview.register import registerView
from django.views.generic.base import TemplateView  # 1、增加该行



urlpatterns = [
    path(admin/,admin.site.urls),path(r‘‘,TemplateView.as_view(template_name=index.html)),#2、 增加该行
    url(r^api/v1/auth/$,AuthView.as_view()),# 登录
    url(r^api/v1/order/$,OrderView.as_view()),# 用户认证
    url(r^api/v1/info/,UserInfoView.as_view()),# 用户权限
    url(r^home/register/$,registerView.as_view()),# 注册
]

?

models

一个保存用户的信息

一个保存用户登录成功后的token

from django.db import models

# Create your models here.

class User(models.Model):
    USER_TYPE = (
        (1,普通用户),(2,VIP),(3,SVIP)
    )

    username = models.CharField(max_length=32,unique=True)
    password = models.CharField(max_length=32)
    age = models.CharField(max_length=32)
    e_mail = models.EmailField()
    user_type = models.IntegerField(choices=USER_TYPE)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)

    def __str__(self):
        return username: %s,password: %s %(self.username,self.password)

        # return ‘username: {},password: {}‘.format(self.username,self.password)
    class Meta:
        db_table = user
        verbose_name = verbose_name_plural = 用户信息表

class userToken(models.Model):
    user = models.OneToOneField(to=User,on_delete=models.DO_NOTHING)
    # 注意:在数据中关联字段名称叫user_id
    token = models.CharField(max_length=60)
    """定义每个数据对象的显示信息"""
    def __unicode__(self):      # return self.user   使用 def __str__(self):报错 , 使用 __unicode__ 就 OK了。
        return  self.user

    """定义每个数据对象的显示信息"""
    def __str__(self):
        return self.token     # 这个返回值是干什么的? 这里 不能写 user 因为user 对应的是 user_id 是int 类型,服务端会报错。

    class Meta:
        db_table =  userToken
        verbose_name = verbose_name_plural = 用户token表




#    def __str__(self):   的作用
#         return ‘username: %s,password: %s‘ %(self.username,self.password)

# 在终端中执行  python manage.py shell
# from api.models import User
# User.objects.get(id=1)
# 即可返回如下 信息,
# <User: username: wang,password: 123456>

?

views

用户登录(返回token并保存到数据库)

from django.shortcuts import render

# Create your views here.

import time
from api import models
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from django.shortcuts import render,HttpResponse

from api.utils.permission import SVIPPermission,MyPermission



ORDER_DICT = {

    1:{
        name:apple,price:15
    },2:{
        name:orange,price:30
    }
}



def md5(user):
    import hashlib
    import time
    ctime = str(time.time())
    print(ctime)
    m = hashlib.md5(bytes(user,encoding=utf-8))
    print(m)
    m.update(bytes(ctime,encoding=utf-8))
    print(m)
    usertoken = m.hexdigest()
    print(usertoken)

    return usertoken



class AuthView(APIView):

    authentication_classes = []  # 里面为空,代表不需要认证
    permission_classes = []
    def post(self,request,*args,**kwargs):
        print(参数,request)

        ret = {code:1000,msg:None,token:None}
        try:
            # 参数是datadict 形式
            usr = request.data.get(username)
            pas = request.data.get(password)

            # usr = request._request.POST.get(‘username‘)
            # pas = request._request.POST.get(‘password‘)

            # usr = request.POST.get(‘username‘)
            # pas = request.POST.get(‘password‘)

            print(usr)
            print(pas)
            # obj = models.User.objects.filter(username=‘yang‘,password=‘123456‘).first()
            obj = models.User.objects.filter(username=usr,password=pas).first()
            # obk =models.userToken.objects.filter(token=‘9c979c316d4ea42fd998ddf7e8895aa4‘).first()
            # print(obk.token)
            print(******)
            print(obj)
            print(type(obj))
            print(obj.username)
            print(obj.password)
            if not obj:
                ret[code] = 1001
                ret[msg] = 用户名或者密码错误
                return JsonResponse(ret)
                # 里为了简单,应该是进行加密,再加上其他参数
            # token = str(time.time()) + usr
            token = md5(usr)
            print(token)
            models.userToken.objects.update_or_create(user=obj,defaults={token: token})
            ret[token] = token
            ret[msg] = 登录成功
            #ret[‘token‘] = token
        except Exception as e:
            ret[code] = 1002
            ret[msg] = 请求异常
        return JsonResponse(ret)

?

注册模块

api/appview/register.py

# FileName : register.py
# Author   : Adil
# DateTime : 2019/7/19 5:52 PM
# SoftWare : PyCharm

from api import models
from django.http import JsonResponse
from rest_framework.views import APIView
import time
from api.common.DBHandle import DataBaseHandle
import datetime



class registerView(APIView):

    def changeinfo(self,*args):
        pass


    def post(self,request)
        print(request.data)
        ret = {code: 1000,msg: None}
        try:
            # 参数是datadict 形式
            usr = request.data.get(username)
            pas = request.data.get(password)
            age = request.data.get(age)
            user_type = request.data.get(user_type)
            e_mail = request.data.get(email)

            localTime = time.localtime(time.time())
            tmp_time = time.strftime("%Y-%m-%d %H:%M:%S",localTime)
            print(usr)
            print(tmp_time)
            print(e_mail)
            ct = tmp_time
            ut = tmp_time
            host = 127.0.0.1
            username = username
            password = ‘password
            database = ‘dbname
            port = 3306
            # 实例化 数据库 连接
            dbcon = DataBaseHandle(host,username,password,database,port)

            sql = "select username from user where username = ‘%s‘ " %(usr)

            l1 = dbcon.selectDb(sql)
            print(age)
            if l1==1:
                ret[msg] = 用户已存在!

            else:
                # obj = models.User.objects.filter(username=usr,password=pas).first()
                #print(obj.age)
                # models.User.objects.update_or_create(username=‘yang‘,password=‘123456‘,age=‘19‘)
                print(else)
                print(usr,pas,age,e_mail,ct,ut)
                #sql = "insert into user(username,e_mail) values (‘%s‘,‘%s‘,‘%s‘)" % (usr,e_mail)
                # models.User.objects.update_or_create(username=usr,password=pas,age=age,e_mail= e_mail,create_time=ct,update_time=ut)
                # obj = models.User.objects.filter(username=‘yang‘,password=‘123456‘).first()
                # tt = dbcon.insertDB(sql)
                user = models.User(username=usr,user_type=user_type,e_mail= e_mail)
                user.save()

                print(models.User.objects.all())
                print(*******)
                ret[msg] = 注册成功
                # if tt==1:
                #     ret[‘msg‘] = ‘注册成功‘

        except Exception as e:
            ret[code] = 1002
            ret[msg] = 请求异常
        return JsonResponse(ret)

?

利用postman发请求

注册

?

?

注册成功后,数据存入user表

?

登录模块?

?

?

?userToken数据表

?

添加认证

?基于上面的例子,添加一个认证的类

urls

path(api/v1/order/,

?

views

?

from django.shortcuts import render

# Create your views here.

import time
from api import models
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from django.shortcuts import render,defaults={token: token})
            ret[token] = token
            ret[msg] = 登录成功
            #ret[‘token‘] = token
        except Exception as e:
            ret[code] = 1002
            ret[msg] = 请求异常
        return JsonResponse(ret)



class Authentication(APIView):
    ‘‘‘认证‘‘‘

    def authenticate(self,request):
        token = request._request.GET.get(token)
        print(token)
        token_obj = models.userToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed(用户认证失败)
        # 在rest framework内部会将这两个字段赋值给request,以供后续操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self,request):
        pass


class OrderView(APIView):
    ‘‘‘订单业务‘‘‘

    authentication_classes = [Authentication,]    #添加认证

    # permission_classes = []
    def get(self,**kwargs):
        print("~~~~~~")
        print(request.user)
        print(request.auth)
        print("~~~~~~")
        ret = {code:1000,data:None}
        try:
            ret[data] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

?

用postman发get请求

请求的时候没有带token,可以看到会显示“用户认证失败”

?

加上token重新请求

?

?

以上是简单的局部认证。下面介绍一下全局认证

?

全局配置方法:

api文件夹下面新建文件夹utils,再新建auth.py文件,里面写上认证的类

auth.py

# api/utils/auth.py

from rest_framework import exceptions
from api import models


class Authentication(object):
    ‘‘‘用于用户登录验证‘‘‘
    def authenticate(self,request):
        token = request._request.GET.get(token)
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed(用户认证失败)
        #在rest framework内部会将这两个字段赋值给request,以供后续操作使用
        return (token_obj.user,request):
        pass

?

?settings.py

#设置全局认证
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":[api.utils.auth.Authentication,],#里面写你的认证的类的路径
}

在settings里面设置的全局认证,所有业务都需要经过认证,如果想让某个不需要认证,只需要在其中添加下面的代码:

authentication_classes = []    #里面为空,代表不需要认证

?

from django.shortcuts import render

# Create your views here.

import time
from api import models
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from django.shortcuts import render,defaults={token: token})
            ret[token] = token
            ret[msg] = 登录成功
            #ret[‘token‘] = token
        except Exception as e:
            ret[code] = 1002
            ret[msg] = 请求异常
        return JsonResponse(ret)

class OrderView(APIView):
    ‘‘‘订单业务‘‘‘

    # authentication_classes = []

    # permission_classes = []
    def get(self,data:None}
        try:
            ret[data] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

再测试一下我们的代码

不带token发请求

?

带token发请求?

?

?

?

drf的内置认证

?rest_framework里面内置了一些认证,我们自己写的认证类都要继承内置认证类 "BaseAuthentication"

BaseAuthentication源码:

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self,request):
        """
        Authenticate the request and return a two-tuple of (user,token).
        """
        #内置的认证类,authenticate方法,如果不自己写,默认则抛出异常
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self,request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response,or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        #authenticate_header方法,作用是当认证失败的时候,返回的响应头
        pass

修改自己写的认证类

自己写的Authentication必须继承内置认证类BaseAuthentication?

# FileName : auth.py
# Author   : Adil
# DateTime : 2019/7/30 4:29 PM
# SoftWare : PyCharm

from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    ‘‘‘用于用户登录验证‘‘‘
    def authenticate(self,request):
        # token = request._request.GET.get(‘token‘)
        token = request.GET.get(token) #  同 request._request.GET.get(‘token‘)
        # token = request.query_params.get("token")  # 同request.GET.get("token")
        print(***)
        print(token)
        token_obj = models.userToken.objects.filter(token=token).first()
        print(token_obj)
        if not token_obj:
            raise exceptions.AuthenticationFailed(用户认证失败)
        #在rest framework内部会将这两个字段赋值给request,以供后续操作使用
        return (token_obj.user,token_obj)     # 这两个返回值分别对应 models.py 中 userToken 的user和 token

    def authenticate_header(self,request):
        pass

?

其它内置认证类

?

rest_framework里面还内置了其它认证类,我们主要用到的就是BaseAuthentication,剩下的很少用到

?

?

总结

自己写认证类方法梳理

?(1)创建认证类

  • 继承BaseAuthentication? ? --->>1.重写authenticate方法;2.authenticate_header方法直接写pass就可以(这个方法必须写)

(2)authenticate()返回值(三种)

  • None ----->>>当前认证不管,等下一个认证来执行
  • raise exceptions.AuthenticationFailed(‘用户认证失败‘)? ? ? ?#?from rest_framework import exceptions
  • ?有返回值元祖形式:(元素1,元素2)? ? ? #元素1复制给request.user;? 元素2复制给request.auth

?(3)局部使用

  • authentication_classes = [BaseAuthentication,]

(4)全局使用

#设置全局认证
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":[api.utils.auth.Authentication,]
}

源码流程

--->>dispatch

    --封装request

       ---获取定义的认证类(全局/局部),通过列表生成式创建对象 

     ---initial

       ----peform_authentication

         -----request.user? ?(每部循环创建的对象)

?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读