一,认证组件

restframework中的认证组件是使用token实现的,之前我们用到过cookie和session,cookie保存在客户端浏览器中,用来保存不太敏感的数据,session保存在服务器中,用在存储重要的敏感的信息

不过我们在开发数据接口类的web应用中,使用token还是比较多的

token的认证步骤如下

  • 用户登录,获取用户名密码,查询用户表,如果存在该用户,生成token,否则返回错误信息
  • 更新token

(1),局部认证组件:

我们如果使用到token的话,需要再建立一张user表用来存储用户的用户名,密码以及用户状态等信息,另外还需要一张token表来存储用户的token值,user表跟token表时一对一的关系,表详情如下:

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type_entry = (
        (1,'vip'),
        (2,'svip'),
        (3,'vvip')
    )
    user_type = models.IntegerField(choices=user_type_entry)
    

class UserToken(models.Model):
    user = models.OneToOneField('User',on_delete=models.CASCADE)
    token = models.CharField(max_length=128)

然后我们可以另外创建一个py文件,用来放我们获取随机字符串的类,这个随机字符串就当做我们用到的token值,然后需要用到的时候,直接调用就可以了

#get_token.py

import uuid

def get_token():
    random_str = str(uuid.uuid4()).replace('-', '')
    
    return random_str

因为是用户登录认证,所以只需要post接口就可以了,下面是视图的写法:

 1 
 2 class UserView(APIView):
 3     def post(self,request):
 4         # 定义返回消息类型
 5         response = dict()
 6         # 定义需要的用户信息
 7         fields = {'username','password'}
 8         # 定义一个用户信息字典
 9         user_info = dict()
10 
11         # 如果我们需要的用户信息在前端传过来的字典的key中
12         if fields.issubset(set(request.data.keys())):
13             for key in fields:
14                 user_info[key] = request.data[key]
15 
16         # 查询数据库中相关信息
17         user_instance = User.objects.filter(**user_info).first()
18 
19         if user_instance is not None:
20             # 获取随机字符串
21             access_token = get_token()
22             # 去token表中验证有没有该用户的token,有的话更新,没有的话生成
23             UserToken.objects.update_or_create(user=user_instance,defaults={'token':access_token})
24 
25             response['status_code'] = 200
26             response['status_message'] = '登陆成功'
27             response['access_token'] = access_token
28             # 数据库中存的choice,所以用get_display取对应值
29             response['user_role'] = user_instance.get_user_type_display()
30 
31         else:
32             response['status_code'] = 201
33             response['status_message'] = '登录失败,用户名或密码错误'
34 
35         return JsonResponse(response)

定义认证类,另建一个py文件,用于存放认证类

 1 from  rest_framework.authentication import BaseAuthentication
 2 from rest_framework.exceptions import APIException
 3 
 4 from app01.models import UserToken
 5 
 6 
 7 # 定义认证类
 8 class UserAuth(BaseAuthentication):
 9 
10     # 认证逻辑都放在authenticate
11     def authenticate(self, request):
12         # request.query_params 中可以获取?param1=32&param2=23形式的参数.类型为querydict
13         user_token = request.query_params.get('token')
14         try:
15             # 这个地方用get获取数据查不到会报错,所以用try
16             token = UserToken.objects.get(token=user_token)
17             # 以下返回的数据在权限中会用到
18             return token.user,token.token
19         except Exception:
20             raise APIException('没有认证')

然后在views中BookView中使用认证类

1 class BookView(ModelViewSet):
2     authentication_classes = [UserAuth]
3     queryset = Book.objects.all()
4     serializer_class = BookSerializer
多个认证类的实现

并且,我们还可以指定多个认证类,只是需要注意的是,如果需要返回什么数据,请在最后一个认证类中返回,因为如果在前面返回,在self._authentication()方法中会对返回值进行判断,如果不为空,认证的过程就会中止

DRF认证源码剖析
 1 前面的步骤都差不多,我们来看有差别的地方,我们说,request对象是APIView重写的,这个是在dispatch方法里面实现的,继续往后看dispatch方法,我们会看到self.initial方法,就是在这个方法里面,我们会看到认证、权限、频率几个组件的实现:
 2 
 3     执行self.initial()方法
 4     执行self.perform_authentication(request),方法,注意,新的request对象被传递进去了
 5     该方法只有一行request.user,根据之前的经验,解析器(request.data),我们知道这个user肯定也是request对的一个属性方法
 6     所料不错,该方法继续执行self._authenticate(),注意此时的self是request对象
 7     该方法会循环self.authenticators,而这个变量是在重新实例化request对象时通过参数传递的
 8     传递该参数是通过get_authenticatos()的返回值来确定的,它的返回值是
 9         [ auth for auth in self.authentication_classes ]
10         也就是我们的BookView里面定义的那个类变量,也就是认证类
11     一切都明朗了,循环取到认证类,实例化,并且执行它的authenticate方法
12         这就是为什么认证类里面需要有该方法
13         如果没有该方法,认证的逻辑就没办法执行
14         至于类里面的header方法,照着写就行,有兴趣的可以研究源码,这里就不细究了
15     该方法如果执行成功就返回一个元组,执行完毕
16         如果失败,它会捕捉一个APIException
17         如果我们不希望认证通过,可以raise一个APIException
18 
19 这就是认证组件的实现方式,非常简单。
View Code

(2),全局认证组件

如果我们需要对所有的接口都进行认证的话,我们需要用到全局认证组件,此时我们需要在setting中进行配置:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'app01.classes.authentication_classes.UserAuth',
    ),
}

二,权限组件

上面我们已经了解了认证组件的使用方式,下面是权限组件的使用方式,跟认证组件是差不多的,所以下面直接上用法:

 1 #permission_class.py
 2 
 3 # 定义权限类
 4 class UserPerms():
 5     message = '您没有权限访问该数据'
 6     
 7     def has_permission(self,request,view):
 8         if request.user.user_type == 3:
 9             return True
10         else:
11             return False

在视图中加入权限类:

1 class BookView(ModelViewSet):
2     authentication_classes = [UserAuth]
3     permission_class = [UserPerms]
4     queryset = Book.objects.all()
5     serializer_class = BookSerializer

这就是权限组件的使用方式,同理,如果要用到全局权限的话,在settings中配置

 三,频率组件

request.MATA : 获取所有请求头

REMOTE_ADDR: 获取客户端的访问ip地址

HTTP_HOST : 获取服务端ip地址

(1),局部访问频率控制

 1 #ratethrottle.py
 2 
 3 from rest_framework.throttling import SimpleRateThrottle
 4 
 5 
 6 class RateThrottle(SimpleRateThrottle):
 7     rate = '5/m'
 8 
 9     def get_cache_key(self, request, view):
10         return self.get_ident(request)

rate代表访问评率,上面表示每分钟五次,get_cache_key是必须存在的,它的返回值告诉当前频率控制组件要使用什么方式区分访问者(比如ip地址)。

视图中:

1 from .utils.throttles import RateThrottle
2 
3 # Create your views here.
4 
5 
6 class BookView(ModelViewSet):
7     throttle_classes = [ RateThrottle ]
8     queryset = Book.objects.all()
9     serializer_class = BookSerializer

(2),全局访问频率控制组件

首先定义一个频率控制类,必须继承SimpleRateThrottle类:

1 class RateThrottle(SimpleRateThrottle):
2     scope = "visit_rate"
3 
4     def get_cache_key(self, request, view):
5         return self.get_ident(request)

然后我们需要在settings中配置频率控制组件参数:

1 REST_FRAMEWORK = {
2     "DEFAULT_THROTTLE_CLASSES": ('app01.chasses.ratethrottle.RateThrottle',),
3     "DEFAULT_THROTTLE_RATES": {
4         "visit_rate": "5/m"
5     }
6 }