路漫漫其修远兮
吾将上下而求索

drf学习:登录、认证、权限、频率控制

P14day129-01 今日内容概要

P15day129-02 内容回顾

P16day129-03 rest framework之用户登录

P17day129-04 rest framework之基于token实现基本用户认证

P18day129-05 rest framework之认证基本流程源码分析

P19day129-06 rest framework之匿名用户配置

P20day129-07 rest framework之内置基本认证

P21day129-08 rest framework之认证内容总结

P22day129-09 rest framework之权限的基本使用

P23day129-10 rest framework之权限源码流程

P24day129-11 rest framework之权限的内置类

P25day129-12 rest framework之访问频率控制基本实现

P26day129-13 rest framework之访问频率控制源码流程

P27day129-14 rest framework之基于内置类实现访问频率控制

P28day129-15 rest framework之访问频率内容梳理

P29day129-016 今日作业

P14day129-01 今日内容概要
主要讲版本,认证,权限和频率




P15day129-02 内容回顾




P16day129-03 rest framework之用户登录

用自己的方法实现一个用户登录,返回token

这里数据库使用的是mysql

下面是settings文件里面内容
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'drftest',  # 数据库名字
        'USER': 'root',  # 账号
        'PASSWORD': '12345',  # 密码
        'HOST': '192.168.170.20',  # IP
        'PORT': '3306',  # 端口
        # 这里引擎用innodb(默认myisam)
        # 因为后面第三方登录时,要求引擎为INNODB
        # 'OPTIONS':{'init_command': 'SET storage_engine=INNODB'}, #这样设置会报错,改为
        "OPTIONS": {"init_command": "SET default_storage_engine=INNODB;SET sql_mode='STRICT_TRANS_TABLES'"}
    }
}



下面是urls.py文件
urlpatterns = [
    # url(r'^admin/', admin.site.urls),
    url(r'api/v1/auth/$', views.AuthView.as_view())
]

下面是models.py文件 
from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


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


下面是views.py文件

from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models


def md5(user):
    import hashlib
    import time

    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':1000, 'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if obj:
                token = md5(user)
                models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
                ret['token'] = token
            else:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'

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


然后在user表中插入几条示例数据
DROP TABLE IF EXISTS `app01_userinfo`;
CREATE TABLE `app01_userinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_type` int(11) NOT NULL,
  `username` varchar(32) NOT NULL,
  `password` varchar(64) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of app01_userinfo
-- ----------------------------
INSERT INTO `app01_userinfo` VALUES ('1', '1', 'xuhaohua', '123');
INSERT INTO `app01_userinfo` VALUES ('2', '2', 'mingruijie', '1234');
INSERT INTO `app01_userinfo` VALUES ('3', '2', 'wangyuanming', '12345');
INSERT INTO `app01_userinfo` VALUES ('4', '3', 'zhangguikun', '123456');


然后使用postman进行测试
http://127.0.0.1:8000/api/v1/auth/
表单内容填入用户名和密码可以测试




P17day129-04 rest framework之基于token实现基本用户认证

本节实现功能
http://127.0.0.1:8000/api/v1/auth  调用这个接口,输入用户名和密码。正确后返回给用户token
http://127.0.0.1:8000/api/v1/order/?token=b80f0dea945159cbe33467f700bc6f62  用户使用token来查询订单列表,当token错误,返回权限未通过

下面是urls.py内容
from django.conf.urls import url
# from django.contrib import admin
from app01 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()),
]


下面是models.py内容 
from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用户'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
    user_type = models.IntegerField(choices=user_type_choices)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


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


下面是views.py里面的内容
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework import exceptions
from app01 import models

下面是验证登录部分
def md5(user):
    import hashlib
    import time

    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':1000, 'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if obj:
                token = md5(user)
                models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
                ret['token'] = token
            else:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'

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



下面是订单部分,获取订单时会查询token是否有效
ORDER_DICT = {
    1:{
        'name':'apple',
        'color':'blue',
        'price': 18
    },
    2:{
        'name':'banana',
        'color':'yellow',
        'price':20
    }
}


class Authtication(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('用户认证失败')
        return (token_obj.user, token_obj)

    def authenticate_header(self,request):
        pass


class OrderView(APIView):
    authentication_classes = [Authtication,]

    def get(self, request, *args, **kwargs):
        ret = {'code':1000, 'msg':None, 'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass

        print(request.user.username, request.auth.token)
        return JsonResponse(ret)


当还有其他类有需要登录才能操作的需求,只需要引入:authentication_classes = [Authtication,]




P18day129-05 rest framework之认证基本流程源码分析


为什么在函数中直接raise错误,drf会自动捕获
在drf的dispatch方法里面会有try except方法,子类中任何异常,这里都可以捕获,然后执行except里面方法,返回给用户错误信息

def dispatch(self, request, *args, **kwargs):
  
    try:
        xxxxxxx
    except Exception as exc:
        response = self.handle_exception(exc)


为什么最后的元组里面的数据会赋值给user和auth
在最里面的
def _authenticate(self):
    """
    Attempt to authenticate the request using each authentication instance
    in turn.
    """
    for authenticator in self.authenticators:
        #执行认证的方法
        #如果认证方法异常,执行_not_authenticated方法
        #如果认证成功,函数返回的元组赋值给self.user, self.auth
        #返回None,我不管,下一个认证来处理
        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
    如果数组里面是空,则执行认证失败里面方法
    self._not_authenticated()




P19day129-06 rest framework之匿名用户配置

主要看配置还能怎么改

如果settings里面有配置文件,先读这里面的配置文件
可以将上面的在类里面定义的list,放到settings里面,则全局生效
类的名字包括前面的路径,如果有多个认证类,都可以追加到后面去


创建单独的文件来存放认证方法 app01/utils/authen.py,里面添加下面文件

from rest_framework import exceptions
from app01 import models


class MyAuthtication(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('用户认证失败')
        return (token_obj.user, token_obj)

    def authenticate_header(self,request):
        pass

        
然后在views.py里面将原有的注释掉,这里使用全局配置文件
class OrderView(APIView):
    #authentication_classes = [MyAuthtication,]

    def get(self, request, *args, **kwargs):
        #self.dispatch()
        ret = {'code':1000, 'msg':None, 'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass

        print(request.user.username, request.auth.token)
        return JsonResponse(ret)

        
在settings.py中添加
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.utils.authen.MyAuthtication",]
}

可以达到和上面的效果一样,这个是全局配置,所有类都生效
如果某个类不想认证就能访问,比如认证接口,则在认证接口里面单独设置,list置空即可
authentication_classes = []  就能达到效果


坑爹啊,这里有个问题,认证内容必须放到单独的文件中,刚开始放到views里面报错,怎么也找不到问题,
https://stackoverflow.com/questions/30567264/custom-authentication-class-as-a-default-authentication-classes


匿名用户配置
REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': lambda:'匿名用户',
    'UNAUTHENTICATED_TOKEN': None,
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.utils.authen.MyAuthtication",]
}

这样后执行:print(request.user)将会返回:匿名用户




P20day129-07 rest framework之内置基本认证

from rest_framework.authentication import BaseAuthentication
BaseAuthentication里面有几种已经写好的认证方式,可以参考
尤其是 class TokenAuthentication(BaseAuthentication):




P21day129-08 rest framework之认证内容总结

上面的内容总结





P22day129-09 rest framework之权限的基本使用

问题:不同视图只能给不同的权限访问

这里是一个示例,两个类
order类只允许svip用户访问,必须登录
userinfo类只允许vip用户访问,必须登录
这里的操作和登录认证的过程类似

下面是app01.views文件


from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
from app01.utils.permission import MyPermissionVIP, MyPermissionSVIP

def md5(user):
    import hashlib
    import time

    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):
    authentication_classes = []
    def post(self,request, *args, **kwargs):
        ret = {'code':1000, 'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if obj:
                token = md5(user)
                models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
                ret['token'] = token
            else:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'

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


ORDER_DICT = {
    1:{
        'name':'apple',
        'color':'blue',
        'price': 18
    },
    2:{
        'name':'banana',
        'color':'yellow',
        'price':20
    }
}


class OrderView(APIView):
    #订单只允许svip访问
    message = '订单只允许svip访问'
    permission_classes = [MyPermissionSVIP,]
    def get(self, request, *args, **kwargs):
        self.dispatch()
        ret = {'code':1000, 'msg':None, 'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass

        print(request.user.username, request.auth.token)
        return JsonResponse(ret)


class UserInfo(APIView):
    #用户信息只允许vip访问
    message = '用户信息只允许vip访问'
    permission_classes = [MyPermissionVIP,]
    def get(self, request, *args, **kwargs):
        ret = {'user':'test'}
        return JsonResponse(ret)




创建文件utils/permission.py,内容如下
class MyPermissionSVIP(object):
    def has_permission(self, request, view):
        if request.user.user_type == 3:
            return True
        return False


class MyPermissionVIP(object):
    def has_permission(self, request, view):
        if request.user.user_type == 2:
            return True
        return False




P23day129-10 rest framework之权限源码流程
如果没有设置,使用全局的,如果在全局中设置一个
"DEFAULT_PERMISSION_CLASSES":['app01.utils.permission.MyPermissionSVIP']

如果单个类不需要权限就能访问,则在这个类里面将list置空

如果想自定义返回错误信息,在自定义类里面添加message变量即可,
在最里层的函数中会取这里面内容,如果自定义,使用自定义信息




P24day129-11 rest framework之权限的内置类
权限类应该都继承基础类
from rest_framework.permissions import BasePermission

权限类返回值:
    - True:有权访问
    - False:无权访问,里面的方法会抛出无权访问异常



P25day129-12 rest framework之访问频率控制基本实现
P26day129-13 rest framework之访问频率控制源码流程
P27day129-14 rest framework之基于内置类实现访问频率控制
P28day129-15 rest framework之访问频率内容梳理
P29day129-016 今日作业
py程序在反代后面,源ip不好获取,以上内容暂时用不到

未经允许不得转载:江哥架构师笔记 » drf学习:登录、认证、权限、频率控制

分享到:更多 ()

评论 抢沙发

评论前必须登录!