Django rest framework----認證

Django rest framework----認證

 

先了解的一些知識

理解下面兩個知識點很是重要,django-rest-framework源碼中處處都是基於CBV和麪向對象的封裝javascript

(1)面向對象封裝的兩大特性html

把同一類方法封裝到類中

將數據封裝到對象中

(2)CBVjava

CBV(class base views) 就是在視圖裏使用類處理請求。  對應的還有 FBV(function base views) 就是在視圖裏使用函數處理請求。python

基於反射實現根據請求方式不一樣,執行不一樣的方法sql

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

CBV(class base views) 就是在視圖裏使用類處理請求。數據庫

Python是一個面向對象的編程語言,若是隻用函數來開發,有不少面向對象的優勢就錯失了(繼承、封裝、多態)。因此Django在後來加入了Class-Based-View。可讓咱們用類寫View。這樣作的優勢主要下面兩種:django

  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寫的話,就是下面這樣api

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的子類),推薦把View寫在最右邊,多個Mixin寫在左邊。

 mixin模式

class X(object):
  def f(self):
    print( 'x')

class A(X):
  def f(self):
    print('a')

  def extral(self):
    print('extral a')

class B(X):
  def f(self):
    print('b')

  def extral(self):
    print( 'extral b')

class C(A, B, X):
  def f(self):
    super(C, self).f()
    print('c')

print(C.mro())

c = C()
c.f()
c.extral()

這樣作也能夠輸出結果

[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 繼承的順序是 A-->B-->X-->object 這了的object在python3中是一切類的基類,包括object類自己。
a
c
extral a # 雖然類C中沒有實現接口extral(),卻調用了父類A中的extral()方法

這樣的繼承雖然能夠實現功能,可是有一個很明顯的問題,那就是在面向對象中,必定要指明一個類究竟是什麼。也就是說,若是我想構造一個類,假如是Somthing,那麼我想讓這個類實現會飛,會游泳,會跑,三種行爲,我能夠這樣作,同時繼承,鳥,魚,馬三個類,像這樣

class Bird:
  def fly(self):
    print('fly')

class Fish:
  def swim(self):
    print('swim')
    
class Horse:
  def run(self):
    print('run')
    
    
class Something(Bird, Fish, Horse):
  pass


s = Something()
s.fly()
s.swim()
s.run()

輸出

fly
swim
run

但是實現會跑,會飛,會游泳的三種行爲,可是這個類究竟是什麼,是魚,是馬,仍是鳥,也就是說不知道Something究竟是個什麼類。爲了解決這個問題,咱們能夠引用mixin模式。改寫以下

class BirdMixin:
  def fly(self):
    print('fly')

class FishMixin:
  def swim(self):
    print('swim')
    
class Horse:
  def run(self):
    print('run')
    
    
class Something(BirdMixin, FishMixin, Horse):
  pass

這樣就解決了上面的問題,也許你會發現,這其實沒有什麼變化,只是在類的命名加上了以Mixin結尾,其實這是一種默認的聲明,告訴你,Something類實際上是一種馬,父類是HorseHorse,繼承其餘兩個類,只是爲了調用他們的方法而已,這種叫作mixin模式,在drf的源碼種會用到

例如drf中的generics 路徑爲rest_framework/generics.py

class CreateAPIView(mixins.CreateModelMixin,
          GenericAPIView):
  pass


class ListAPIView(mixins.ListModelMixin,
         GenericAPIView):
  pass


class RetrieveAPIView(mixins.RetrieveModelMixin,
           GenericAPIView):
  pass

至關於每多一次繼承,子類可調用的方法就更多了。

 

簡單實例

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  # 一、增長該行



urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'',TemplateView.as_view(template_name='index.html')),  #二、 增長該行
    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,*args,**kwargs):
        print('參數', 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,password,age,e_mail) values ('%s','%s','%s','%s')" % (usr, pas,age,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, password=pas, age=age,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/',OrderView.as_view()),

 

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,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)



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,request,*args,**kwargs):
        print("~~~~~~")
        print(request.user)
        print(request.auth)
        print("~~~~~~")
        ret = {'code':1000,'msg':None,'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,token_obj)

    def authenticate_header(self, 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,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)

class OrderView(APIView):
    '''訂單業務'''

    # authentication_classes = []

    # permission_classes = []
    def get(self,request,*args,**kwargs):
        print("~~~~~~")
        print(request.user)
        print(request.auth)
        print("~~~~~~")
        ret = {'code':1000,'msg':None,'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   (每部循環建立的對象)

 
相關文章
相關標籤/搜索