咱们知道,在django项目中无论路由以及对应的视图类是如何写的,都会走到 dispatch
方法,进行路由分发,vue
在阅读 APIView类中的dispatch
方法的源码中,有个 self.initial(request, *args, **kwargs)
,能够发现认证、权限、频率这三个默认组件都在这个方法里面,若是咱们本身没有定义这三个组件的配置,那么就会使用源码中默认的一些配置。python
源码:数据库
# Ensure that the incoming request is permitted #实现认证 self.perform_authentication(request) #权限判断 self.check_permissions(request) #控制访问频率 elf.check_throttles(request)
目前为止你们知道的认证机制是否是有cookie、session啊,session更安全一些,可是你会发现session的信息都存到我们的服务器上了,若是用户量很大的话,服务器压力是比较大的,而且django的session存到了django_session表中,不是很好操做,可是通常的场景都是没有啥问题的,如今生产中使用的一个叫作token机制的方式比较多,如今咱们是否是就知道个csrf_token啊,其实token有不少种写法,如何加密,你是hashlib啊仍是base64啊仍是hmac啊等,是否是加上过时时间啊,是否是要加上一个secret_key(客户端与服务端协商好的一个字符串,做为双方的认证依据),是否是要持续刷新啊(有效时间要短,不断的更新token,若是在这么短的时间内仍是被别人拿走了token,模拟了用户状态,那这个基本是没有办法的,可是你能够在网络或者网络设备中加安全,存客户的ip地址等,防黑客)等等。django
大体流程图解:json
首先咱们须要建立一个表,用户表,里面放一个token字段,其实通常我都是放到两个表里面,和用户表是一个一对一关系的表,看代码:后端
models.py内容以下:安全
################################# user表 ############################### class User(models.Model): user = models.CharField(max_length=32) pwd = models.CharField(max_length=32) type_choice=((1,"VIP"),(2,"SVIP"),(3,"SSVIP")) user_type = models.IntegerField(choices=type_choice) class UserToken(models.Model): user = models.OneToOneField(to=User) #一对一到用户表 token = models.CharField(max_length=128) #设置的长度大一些 # expire_time = models.DateTimeField() #若是作超时时间限制,能够在这里加个字段
urls.py内容以下:服务器
#登录认证接口 url(r'^login/$', views.LoginView.as_view(),), #别忘了$符号结尾
views.py内容以下:每次登录成功以后刷新token值cookie
###################login逻辑接口####################### #关于逻辑接口而不是提供数据的接口,咱们不用ModelViewSet,而是直接写个类,继承APIView,而后在类里面直接写咱的逻辑 import uuid import os import json class LoginView(APIView): #从先后端分离的项目来说,get请求不须要写,由于get就是个要登录页面的操做,vue就搞定了,因此咱们这里直接写post请求就能够了 def post(self,request): # 通常,请求过来以后,咱们后端作出的响应,都是个字典,不只包含错误信息,还有要状态码等,让客户端明白到底发生了什么事情 # 'code'的值,1表示成功,0表示失败,2表示其余错误(本身能够作更细致的错误代码昂) res = {'code': 1, 'msg': None, 'user': None,'token':None} print(request.data) try: user = request.data.get('user') pwd = request.data.get('pwd') # 数据库中查询 user_obj = models.User.objects.filter(user=user, pwd=pwd).first() if user_obj: res['user'] = user_obj.user # 添加token,用到我们usertoken表 # models.UserToken.objects.create(user=user,token='123456') # 建立token随机字符串,我写了两个方式,简写的昂,最好再加密一下 random_str = uuid.uuid4() # random_str = os.urandom(16) bytes类型的16位的随机字符串 models.UserToken.objects.update_or_create( user=user_obj, # 查找筛选条件 defaults={ # 添加或者更新的数据 "token": random_str, } ) res['token'] = random_str res['msg'] = '登录成功' else: res['code'] = 0 res['msg'] = '用户名或者密码错误' return Response(res) except Exception as e: res['code'] = 2 res['msg'] = str(e) return Response(res)
经过上面的代码咱们将token返回给了用户,那么之后用户无论发送什么请求,都要带着我给它的token值来访问,认证token经过才行,而且更新token。网络
未来有些数据接口是必需要求用户登录以后才能获取到数据,因此未来用户登录完成以后,每次再过来请求,都要带着token来,做为身份认证的依据。
在DRF的全局配置信息中,有DRF内置组件的相关配置 :
# 基于django基础配置信息上的drf框架的配置信息 REST_FRAMEWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', ), ... }
补充:
django项目的request对象中有一个user属性,是一个默认的假用户
AnonymousUser
,当用户未登陆admin后台管理系统时,request.user = AnonymousUser
若登陆了admin后台,在django内置的认证系统中,request.user = 当前登陆用户
DRF中内置的认证系统
'rest_framework.authentication.SessionAuthentication'
跟django中的认证系统挂钩的,使用的也是admin后台的用户信息
DRF内置组件依据session信息作认证,根据上面的叙述,session不适合作大型项目的认证载体,过重了!因此咱们能够借助DRF内置的认证系统进行自定义认证系统。
如下是其认证机制的部分源码实现:
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是APIView封装的新的request对象 self.user, self.auth = user_auth_tuple return #退出了这个函数,函数就不会执行了,不会再循环了,因此若是你的第一个认证类有返回值,那么第二个认证类就不会执行了,因此别忘了return是结束函数的意思,因此若是你有多个认证类,那么返回值放到最后一个类里面
from rest_framework.authentication import BaseAuthentication # 'BaseAuthentication'这个类源码实现中有个'authenticate'方法,咱们能够重写这个类进行自定制我们的认证系统。 """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ raise NotImplementedError(".authenticate() must be overridden.") """
1、基于drf提供的认证类BaseAuthentication进行自定制认证系统
一、自定义认证类MyAuthentication,继承自BaseAuthentication类且重写其authenticate方法
# utils.auth.py from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class MyAuthentication(BaseAuthentication): def authenticate(self, request): if 1: return "aliang", "fengting007" else: return AuthenticationFailed("认证失败!")
二、全局配置
对自定义的认证类MyAuthentication中的认证流程进行全局配置,即在访问每一个url时都要通过自定义的认证逻辑
# 基于django基础配置信息上的drf框架的配置信息 REST_FRAMEWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( # 自定义认证类的路径 'four.utils.auth.MyAuthentication', # 注释掉drf内置的session认证系统,使用本身定制的! # 'rest_framework.authentication.SessionAuthentication', ), ... }
三、局部配置
即在访问某个指定的url时,须要认证后才能够获取到响应页面,而不是访问全部url都须要认证,这就要在指定的url所映射的类视图中进行定制
from rest_framework.views import APIView from rest_framework.response import Response from four.utils.auth import MyAuthentication class AuthAPIView(APIView): # 访问AuthAPIView类视图所对应的url请求响应页面时,要通过自定制的MyAuthentication类的认证逻辑! authentication_classes = [MyAuthentication,] def get(self, request): print(request.user) # 'aliang' print(request.auth) # 'fengting007' return Response({'msg':'嗨,man!'})
2、不继承drf提供的认证类BaseAuthentication自定制认证类
class MyAuthentication(): def authenticate_header(self,request): pass # authenticate方法是固定的,而且必须有个参数,这个参数是新的request对象 def authenticate(self, request): if 1: # 源码中会发现,这个方法会有两个返回值,而且这两个返回值封装到了新的request对象中了,request.user-->用户名 和 request.auth-->token值,这两个值做为认证结束后的返回结果 # 以下操做即: # request.user = 'aliang' # request.auth = 'fengting007' return "aliang", "fengting007"
至于此自定义认证类的全局配置以及局部配置的方法与上面的流程是一致的。
# 局部配置示例: from app01.serializer import BookSerializers from four.utils.auth import MyAuthentication class BookView(APIView): #认证组件确定是在get、post等方法执行以前执行的,源码中这个组件是在dispatch的地方调用的 authentication_classes = [MyAuthentication,] #认证类能够写多个,一个一个的顺序验证 def get(self,request): #这样就拿到了上面MyAuthentication类的authenticate方法的两个返回值 print(request.user) print(request.auth) book_obj_list = models.Book.objects.all() s_books = BookSerializers(book_obj_list,many=True) return Response(s_books.data)