美文网首页程序员
Rest framework-认证,权限,频率组件

Rest framework-认证,权限,频率组件

作者: 墨颜丶 | 来源:发表于2018-08-07 01:43 被阅读0次

Rest framework-认证,权限,频率组件

认证,权限,频率这三个都是一样的实现逻辑

下面着重介绍认证组件,会了认证组件,权限,频率自然一通百通

大家主要看我写有注视的地方,其他选择性观看

不管什么请求来了一定都会走dispatch方法进行分发

# APIView下的dispatch
def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    # 接收Django的request生成新的request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        # 认证,权限,频率都在这句话里面
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response
def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    # 认证组件
    self.perform_authentication(request)
    # 权限组件
    self.check_permissions(request)
    # 频率组件
    self.check_throttles(request)

下面开分析这三个组件

    # 认证组件
    self.perform_authentication(request)
    # 权限组件
    self.check_permissions(request)
    # 频率组件
    self.check_throttles(request)

认证组件源码简单分析

def perform_authentication(self, request):
    """
    Perform authentication on the incoming request.

    Note that if you override this and simply 'pass', then authentication
    will instead be performed lazily, the first time either
    `request.user` or `request.auth` is accessed.
    """
    # 下面可以看出这是request下的一个user方法
    # perform_authentication在APIView的dispatch下,所以是新的request
    request.user
request = self.initialize_request(request, *args, **kwargs)
# 下一步 self.initialize_request
def initialize_request(self, request, *args, **kwargs):
    """
        Returns the initial request object.
        """
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )
# 下一步 Request
    # 必须有@property下面的user才是我们要找的
    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
    
        if not hasattr(self, '_user'):
            # 没有_user就执行_authenticate
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

# 下一步
    # 此时的self是Request
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        # 此时的self是Request
        # 所以我们去Request里边找authenticators
        # self.authenticators = authenticators or () 这个变量是Request参数传进来的,看下图
        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

        self._not_authenticated()

return Request(
    request,
    parsers=self.get_parsers(),
    # authenticators = 此时的self是我们自己写的视图类.get_authenticators()
    authenticators=self.get_authenticators(),
    negotiator=self.get_content_negotiator(),
    parser_context=parser_context
)

# 下一步 下面基本可以看出 解析,认证,权限,频率基本都是一样的实现逻辑
    # 解析组件
    def get_parsers(self):
        return [parser() for parser in self.parser_classes]
    # 认证组件
    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]
    # 权限组件
    def get_permissions(self):
        return [permission() for permission in self.permission_classes]
    # 频率组件
    def get_throttles(self):
        return [throttle() for throttle in self.throttle_classes]


# 下一步
# 自己写的视图类首先继承了APIView,在APIView找到了authentication_classes
class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    # authentication_classes
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

结论:

Rest framework-解析器组件

按照之前解释器分析,从这里我们可以看出如果自己在自己的视图内部定义了authentication_classes,就优先执行局部的authentication_classes,局部没有就优先执行全局settings.py,全局没有就默认的

认证组件应用

我们自己写一个简单的csrf_token认证的登陆接口

urls.py

url(r'^login/$', views.LoginView.as_view()),
url(r'^courses/$', views.CourseView.as_view()),

models.py

class User(models.Model):
    user=models.CharField(max_length=18)
    pwd=models.CharField(max_length=32)

class UserToken(models.Model):
    user=models.OneToOneField("user")
    token=models.CharField(max_length=128)

# 自己手动添加了两个用户
user:moyan  pwd:123
user:allen  pwe:234

views.py

# 生成随机字符串
def get_random_str(user):
    import hashlib,time
    ctime=str(time.time())
    md5=hashlib.md5(bytes(user,encoding="utf8"))
    md5.update(bytes(ctime,encoding="utf8"))

    return md5.hexdigest()

from app01.models import User,UserToken
from django.http import JsonResponse
class LoginView(APIView):
    def post(self,request):
        res = {"code": 1000, "msg": None}
        try:
            # 接收的JSON数据所以是data不是POST,
            user = request.data.get("user")
            pwd = request.data.get("pwd")
            user_obj = User.objects.filter(user=user, pwd=pwd).first()
            if not user_obj:
                res["code"] = 1001
                res["msg"] = "用户名或者密码错误"
            else:
                # 生成随机字符串
                token = get_random_str(user)
                # update_or_create 更新还是创建取决于UserToken表里有user=user_obj则更新,没有则创建
                UserToken.objects.update_or_create(user=user_obj, defaults={"token": token})
                res["token"] = token

        except Exception as e:
            res["code"] = 1002
            res["msg"] = str(e)

        return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

测试:http://127.0.0.1:8000/login/

# 请求
{
    "user":"moyan",
    "pwd":"123"
}
# 账户密码正确返回 下次在以moyan更新token,即第一次创建,后边则为更新
{
    "code": 1000,
    "msg": null,
    "token": "c6c7b5346bc34208e4d885750c95aff4"
}
# 失败返回
{
    "code": 1001,
    "msg": "用户名或者密码错误"
}

自己写的认证类

# rest_framework认证错误类
from rest_framework.exceptions import AuthenticationFailed
class TokenAuth(object):
    # 必须叫 authenticate
    def authenticate(self,request):
        # 获取用户携带过来的token
        token=request.GET.get("token",None)
        # 过滤获取用户对象
        token_obj=UserToken.objects.filter(token=token).first()
        if token_obj:
            # 如果非要返回元祖的话,一定要是最后一个认证类
            # 因为分会元祖,源码就直接return了,不在执行之后的认证类了
            return token_obj.user.user,token_obj
        else:
            raise AuthenticationFailed("认证失败! ")

    # 自己写的认证类里边必须有authenticate_header,但是里边可以什么都不写
    def authenticate_header(self,request):
        pass

自己写的认证类升级版

from rest_framework.exceptions import AuthenticationFailed
from app01.models import UserToken
from rest_framework.authentication import BaseAuthentication
# 继承BaseAuthentication就不用里边有写的authenticate_header
# BaseAuthentication里有写authenticate,authenticate_header这两个方法,方法内什么都没写
# 所以自己写的话覆盖,没写继承
class TokenAuth(BaseAuthentication):
    # 必须叫 authenticate,覆盖BaseAuthentication里边的
    def authenticate(self,request):
        # 获取用户携带过来的token
        token=request.GET.get("token",None)
        # 过滤获取用户对象
        token_obj=UserToken.objects.filter(token=token).first()
        if token_obj:
            # 如果非要返回元祖的话,一定要是最后一个认证类
            # 因为分会元祖,源码就直接return了,不在执行之后的认证类了
            return token_obj.user.user,token_obj.token
        else:
            raise AuthenticationFailed("认证失败! ")

以下是BaseAuthentication源码,除了BaseAuthentication还有很多REST有的认证类(SessionAuthentication,TokenAuthentication等),跟BaseAuthentication在同一文件下,但是基本没用过,每个业务认证都是不同的,所以都得重写

class BaseAuthentication(object):
    def authenticate(self, request):
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        pass

局部认证

class CourseView(APIView):

    # 列表就是放多个认证类的意思
    authentication_classes=[TokenAuth,]

    def get(self,request):
        return HttpResponse("get....")

    def post(self,request):
        print(request.data)
        return HttpResponse("post.....")

以后访问courses,必须携带usertoken表里存在的token值

测试:http://127.0.0.1:8000/courses/?token=fe8d8fe240142c54c00f12655418e88f

全局认证类

settings.py

参考这个默认值authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

REST_FRAMEWORK={
    # 解析
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    ),
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES':(
        "app01.utils.auth.TokenAuth"
    )
}

权限类

models.py

class User(models.Model):
    user=models.CharField(max_length=18)
    pwd=models.CharField(max_length=32)
    # 添加个权限字段
    # user.type 只能点出来数字
    type=models.IntegerField(choices=((1,"common"),(2,"VIP"),(3,"SVIP")),default=1)

class UserToken(models.Model):
    user=models.OneToOneField("user")
    token=models.CharField(max_length=128)
    # 测试数据
    # 1 moyan   123 1
    # 2 allen   234 3

源码,还记得这条语句么 ? 还是先去自己的视图类里面找有没有permission_classes

# 下一步 下面基本可以看出 解析,认证,权限,频率基本都是一样的实现逻辑
    # 解析组件
    def get_parsers(self):
        return [parser() for parser in self.parser_classes]
    # 认证组件
    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]
    # 权限组件
    def get_permissions(self):
        return [permission() for permission in self.permission_classes]
    # 频率组件
    def get_throttles(self):
        return [throttle() for throttle in self.throttle_classes]

views.py

class CourseView(APIView):


    # 列表就是放多个认证类的意思
    authentication_classes=[TokenAuth,]
    permission_classes = [SVIPPermission,]

    def get(self,request):
        return HttpResponse("get....")

    def post(self,request):
        print(request.data)
        return HttpResponse("post.....")

自己的权限类怎么写,还得取决于调用的这三个方法里边怎么写的

源码

def initial(self, request, *args, **kwargs):
    # ..........
    self.perform_authentication(request)
    self.check_permissions(request)
    self.check_throttles(request)
def check_permissions(self, request):
    """
    Check if the request should be permitted.
    Raises an appropriate exception if the request is not permitted.
    """
    for permission in self.get_permissions():
        # 自己类的函数名字必须是has_permission了
        # 只需要返回True 或者 Fales,让if判断
        if not permission.has_permission(request, self):
            self.permission_denied(
                # 不通过返回错误信息,视图类下没有message用默认的
                request, message=getattr(permission, 'message', None)
            )

自己写的权限类

class SVIPPermission():
    # 此处的view是执行此函数的视图类
    def has_permission(self,request,view):
        user_type=request.auth.user.type
        if user_type==3:
            return True
        else:
            return False

测试用的是moyan的token:http://127.0.0.1:8000/courses/?token=fe8d8fe240142c54c00f12655418e88f

{
    "detail": "权限不够"
}

测试用的是allen的token:http://127.0.0.1:8000/courses/?token=428271f7e71624f052bc205867c659f

# 成功
get....

全局权限

settings.py

REST_FRAMEWORK={
    # 解析
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES':(
        # 认证类路径  我只是把他们都暂时放到一个文件里面去了
        "app01.utils.auth.TokenAuth"
    ),
    # 权限
    'DEFAULT_PERMISSION_CLASSES':(
        # 权限类路径
        "app01.utils.auth.SVIPPermission"

    ),
}

频率类

自己的权限类怎么写,还得取决于调用的这三个方法里边怎么写的

源码

def initial(self, request, *args, **kwargs):
    # ..........
    self.perform_authentication(request)
    self.check_permissions(request)
    self.check_throttles(request)
    def check_throttles(self, request):
        for throttle in self.get_throttles():
            # 自己类的函数名字必须是has_permission了
            # 只需要返回True 或者 Fales,让if判断
            if not throttle.allow_request(request, self):
                self.throttled(request, throttle.wait())

REST频率类

# SimpleRateThrottle 简单的频率限制
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
    scope = "visit_rate"
    def get_cache_key(self, request, view):
        return self.get_ident(request)

全局

REST_FRAMEWORK={
    # # 解析
    # 'DEFAULT_PARSER_CLASSES': (
    #     'rest_framework.parsers.JSONParser',
    #     'rest_framework.parsers.FormParser',
    #     'rest_framework.parsers.MultiPartParser'
    # ),
    # # 认证
    # 'DEFAULT_AUTHENTICATION_CLASSES':(
    #     "app01.utils.auth.TokenAuth"
    # ),
    # # 权限
    # 'DEFAULT_PERMISSION_CLASSES':(
    #     "app01.utils.auth.SVIPPermission"
    #
    # ),
    # 频率
    "DEFAULT_THROTTLE_CLASSES": (
        "app01.utils.auth.VisitThrottle",
    ),
    # 频率配置
    "DEFAULT_THROTTLE_RATES": {
        # 每分钟5次 可以 5/s秒 5/d 等
        # visit_rate 对应 scope="visit_rate"
        "visit_rate": "5/m",
    }
}

局部

class CourseView(APIView):


    # # 列表就是放多个认证类的意思
    # authentication_classes=[TokenAuth,]
    # permission_classes = [SVIPPermission,]
    throttle_classes = [VisitThrottle]

    def get(self,request):
        return HttpResponse("get....")

    def post(self,request):
        print(request.data)
        return HttpResponse("post.....")

排除个别视图类不参加认证

先走认证才能到权限之后是频率

全局每个视图类都需要认证,包括登陆在内.可是登陆成功才后生成token,现在登陆都得携带token,很不合理;需要在全局中排除掉单个视图类不参加全局认证,怎么写呢?

eg:不想让哪个视图类,参加全局认证,只需要在自己的视图类中写一个局部认证,你可以什么都不写

其他不参加权限,频率,全局等同理,写个空的局部函数,或者执行自己的局部认证

记住先走局部-全局-默认

class LoginView(APIView):
    authentication_classes = []
    def post(self,request):
        # ...........

相关文章

网友评论

    本文标题:Rest framework-认证,权限,频率组件

    本文链接:https://www.haomeiwen.com/subject/jegnvftx.html