Django Rest Framework(二) 之用户认证及源码流程

全局认证和局部认证及源码流程和内部认证

 

自定义局部认证类

models.py

from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=32,unique=True)
    password = models.CharField(max_length=64)
    user_type_choices=(
        (1,'普通用户'),
        (2,'vip用户'),
        (3,'超级用户'),
    )

    user_type = models.IntegerField(choices=user_type_choices)


class UserToken(models.Model):
    user = models.OneToOneField("UserInfo")
    token = models.CharField(max_length=64)
models.py

 

views.py

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from api_test import models
import time,hashlib
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
#测试数据 order_dict = { 1:{ "name":"apple", 'price':'15', 'addr':'bj', 'date':'2018-06-01' }, 2:{ "name":"tomato", 'price':'5', 'addr':'bj', 'date':'2018-06-01' } } #生成token的函数 def md5(user): ctime = str(time.time()) m = hashlib.md5(bytes(user,encoding="utf-8")) m.update(bytes(ctime,encoding="utf-8")) return m.hexdigest() class AuthView(APIView): ''' 用户登录认证 ''' def post(self,request,*args,**kwargs): ret = {'code':10000,'msg':None}              #返回的状态码和相关信息设置 try: user = request._request.POST.get("username") #因为这个类继承的Apiview,经过处理后的request(不是原生的),需要通过request._request才能获取原生request内的值,具体看Request函数(上一篇文章) pwd = request._request.POST.get("password") obj = models.UserInfo.objects.filter(username=user,password=pwd).first()  #验证用户名和密码 if not obj: ret['code'] = 10001 ret['msg'] = "用户名密码错误"
       #如果用户不存在,则创建token,如果用户存在,则更新token token = md5(user) models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})    #更新或者创建的方法 ret['token'] = token except Exception as e: ret['code'] = 10002 ret['msg'] = "请求异常" return JsonResponse(ret)

#自定义用户认证类
class Authication(BaseAuthentication):   #用户认证的函数名称任意,但是下面认证函数名必须为authenticate(因为cbv源码里执行流程的认证函数为这个名),不明白原理,可看上一篇文章 def authenticate(self,request): current_token = request._request.GET.get('token')      #获取原生request里的token与数据库里的token做判断 obj_token = models.UserToken.objects.filter(token=current_token).first() if not obj_token: raise exceptions.AuthenticationFailed("用户认证失败") #在rest_framework内部会将这两个字段赋值给request,以后供后续操作使用 return (obj_token.user,obj_token) def authenticate_header(self, *args): pass class OrderView(APIView): ''' 订单业务接口 ''' authentication_classes = [Authication,]          #引用上面的认证实例进行认证 def get(self,request,*args,**kwargs): print (request.user,request.auth)         #打印用户信息 ret = {'code': 10000, 'msg': None} try: ret['data'] = order_dict except Exception as e: pass return JsonResponse(ret) def post(self,request,*args,**kwargs): pass def put(self,request,*args,**kwargs): pass def delete(self,request,*args,**kwargs): pass

 

这样需要每定义一个函数都要加上 

authentication_classes = [Authication,]

 

urls.py

from django.conf.urls import url
from django.contrib import admin
from api_test import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/v1/auth/$',views.AuthView.as_view()),
    url(r'^api/v1/order/$',views.OrderView.as_view()),
]

 

源码流程

用户认证流程是在self.dispatch下的self.initial下 self.perform_authentication(request) 函数进行的,此函数调用的是 rest_framework.request.Request类下的user方法user方法调用 self._authenticate()方法,下面对其进行分析

 

_authenticate方法

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:  #①
            try:
                user_auth_tuple = authenticator.authenticate(self)  #②
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:      #③
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

 

再看 _not_authenticated方法

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.

        Defaults are None, AnonymousUser & None.
        """
        self._authenticator = None

        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()      #没有认证时,返回AnnoymousUser 匿名用户
        else:  
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()    #没有认证时返回None
        else:
            self.auth = None

 

说明

①、这里的self.authenticators里面的值由最开始传入Request类的authenticators参数带入,而authenticators参数的值,最终由APIView类下的authentication_classes代入(self.dispatch ---> self.initialize_request ---> self.get_authenticators ---> self.authentication_classes),如果自己在view函数定义了这个authentication_classes对象,默认会用自己自定义的
    
②、执行authentication_classes 这里引入的类下面的authenticate方法 
  A、如果有authenticate 方法抛出异常,执行self._not_authenticated()方法
  B、如果没有异常,有返回值,则执行步骤③将步骤②的返回值 user_auth_tuple 赋给 self.user和self.auth(即 request.user和request.auth,可在views里打印)
  C、没有异常,没有返回值,则会循环执行authentication_classes下的所有认证方法,直到执行完毕,return则终止本次循环

 

自定义全局认证类

分析源码发现认证函数定义在APIView下的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES,查看api_settings源码

rest_framework/settings.py

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']      
    if setting == 'REST_FRAMEWORK':    #可以在django全局的settings.py里定义这个REST_FRAMEWORK的值,做全局认证,如下:
        api_settings.reload()

 

settings.py

import utils  #①
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['utils.auth.Authication']    #②
}

 

此时,在views.py里默认定义的所有方法都会加载这个认证类如果不想认证,则在函数下直接加上

authentication_classes = []

即可,这样就不需要进行认证了

 

说明

①、这里要说明的是,认证的类此时不能写在views下面,需要自定义一个路径,所以就在项目同级目录创建了一个 utils目录,并创建认证文件auth.py,将认证的类写到这个文件下

②、这里为什么 REST_FRAMEWORK 为字典,并且键值为DEFAULT_AUTHENTICATION_CLASSES,是由authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES得到
  看源码分析

 

另外

可以自定义认证失败时的匿名用户和token信息为None,如下

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.

        Defaults are None, AnonymousUser & None.
        """
        self._authenticator = None

        if api_settings.UNAUTHENTICATED_USER:          
            self.user = api_settings.UNAUTHENTICATED_USER()    #自定义此处返回的值
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()     #自定义此处返回的值
        else:
            self.auth = None

 

settings.py(一般不关注)

import utils  #
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['utils.auth.Authication']    #
    "UNAUTHENTICATED_USER":"None",
    "UNAUTHENTICATED_TOKEN" :"None"
}

 

内部认证

rest_framework内部的认证类

from rest_framework.authentication import BaseAuthentication

 

查看 BaseAuthentication源码

class BaseAuthentication(object):

    def authenticate(self, request):        #默认如果为定义会抛出异常
        
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):    #返回的http头部信息
        
        pass

所以每次在views里自定义认证类的时候,都要重写 authenticate 函数和authenticate_header函数,并且继承 BaseAuthentication类

 

posted @ 2018-06-22 15:11  FRESHMANS  阅读(232)  评论(0)    收藏  举报