zoukankan      html  css  js  c++  java
  • Django框架(十九)--Django rest_framework-认证组件

    一、什么是认证

    只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件

    二、利用token记录认证过的用户

    1、什么是token

    token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。session的存储是需要空间的,session的传输一般都是通过cookie来传输,或url重写的方式。
    token在服务器时可以不用存储用户信息的,token传递的方式也不限于cookie传递,token也可以保存起来

    2、token的原理

    当第一次登录认证过后,就会返回一个token到前台,前台之后发送请求,就会带上这个token字符串,

    3、cookie、session、token的区别

    cookie是保存在浏览器,以key:value的形式传递到服务器认证用户,用户名密码可能裸露,不安全
    session是保存在服务器,产生随机字符串与用户信息对应,将随机字符串放在cookie中。服务器会保存一份,可能保存到缓存/数据库/文件
    token发送请求的对象可以是浏览器,也可以是移动端。服务器不需要记录任何东西,每次都是一个无状态的请求,每次都是通过解密来验证是否合法

    三、drf的认证组件

    # 用户信息表
    class UserInfo(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32)
    
    # 用户token
    class UserToken(models.Model):
        token = models.CharField(max_length=64)
        user = models.OneToOneField(to=UserInfo)

    1、基本使用

    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import APIException
    from app01 import models
    
    # 定义一个认证类
    class LoginAuth(BaseAuthentication):
        # 重写authenticate方法
        def authenticate(self, request):
            # 从路由中获取token,从request对象中获取,如果token放在header中
            # query_params 是Request封装的原生request.GET
            token = request.query_params.get('token')
            ret = models.UserToken.objects.filter(token=token).first()
            if ret:
                # 能查到,说明认证通过,返回空,或者返回当前的用户对象
                # ret.user就是当前登录用户对象,一旦retrun了,后面的认证类都不执行了
                return ret.user,ret
            # 如果查不到,抛出异常
            raise APIException('用户认证失败')
            
    # view 层
    from rest_framework.views import APIView
    # 获取随机字符串——token
    def get_token(name):
        md = hashlib.md5()
        md.update(str(name).encode('utf-8'))
        return md.hexdigest()
    # 用户登录
    class Login(APIView):
        def post(self, request, *args, **kwargs):
            response = {'status': 100, 'msg': '登录成功'}
            name = request.data.get('name')
            pwd = request.data.get('pwd')
            ret = models.UserInfo.objects.filter(name=name, pwd=pwd).first()
            if ret:
                token = get_token(name)
                # 一旦用户信息校验通过,就产生一个token保存在Token表中
                models.UserToken.objects.create(token=token, user=ret)
                response['token'] = token
            else:
                response['status'] = 101
                response['msg'] = '用户名或密码错误'
            return JsonResponse(response, safe=False)
    
    # 查看所有图书信息    
    class Book(APIView):
        # 指定authentication_classes,会循环列表实例化,进行认证
        # 该方法是局部使用认证
        authentication_classes = [UserAuth.UserAuth, ]
    
        def get(self, request, *args, **kwargs):
            response = {'status': 100, 'msg': '查询成功'}
            ret = models.Book.objects.all()
            ser = MySerializer.BookSerializer(instance=ret, many=True)
            response['data'] = ser.data
            return JsonResponse(response, safe=False)

    2、全局使用、局部使用、局部禁用认证

    (1)全局使用

    • 在settings文件中配置,配置完以后,就无需在视图类中写,已经是所有视图类都需要认证
    • 必须为REST_FRAMEWORK,key值必须为DEFAULT_AUTHENTICATION_CLASSES
    REST_FRAMEWORK={
        'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
    }

    (2)局部使用

    在需要使用的视图类中写,只对当前视图类起认证作用,重新定义authentication_classes

    class Book(APIView):
        # 该方法是局部使用认证
        authentication_classes = [UserAuth.UserAuth, ]
    
        def get(self, request, *args, **kwargs):
            response = {'status': 100, 'msg': '查询成功'}
            ret = models.Book.objects.all()
            ser = MySerializer.BookSerializer(instance=ret, many=True)
            response['data'] = ser.data
            return JsonResponse(response, safe=False)

    (3)局部禁用

    在配置过全局认证以后,有些视图类不需要认证,可以局部禁用认证,只需将authentication_classes定义为空列表即可。例如:登录视图,不应该有认证,就可以局部禁用

    # settings中
    REST_FRAMEWORK={
        'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
    }
    
    # view的视图类中
    class Book(APIView):
        # 该方法是局部使用认证
        authentication_classes = []
    
        def get(self, request, *args, **kwargs):
            response = {'status': 100, 'msg': '查询成功'}
            ret = models.Book.objects.all()
            ser = MySerializer.BookSerializer(instance=ret, many=True)
            response['data'] = ser.data
            return JsonResponse(response, safe=False)

    3、不存数据库的token实现认证

    # 登录以后,会产生一个随机字符串,返回到移动端/PC端,下一次发送请求,就在后面拼上  ?token=asdfasdgasdg|{"id":1},这样只要将后面的id通过加密获取随机字符串,与原字符串比较是否一致即可,不用再讲token存到数据库
    def check_token(token):
        user = None
        ret = True
        try:
            # token 拿到的是  ‘fsdfasdfasd|{"name": "lqz", "id": 1}’
            ll = token.split('|')
            md = haslib.md5()
            md.update(ll[1].encode('utf-8'))
            md5.update(settings.password.encode('utf-8'))
            hex = md.hexdigest()
            if hex == ll[0]:
                user = ll[1]
            else:
                ret = False
         except Exception as e:
            ret = False
         return ret,user
    
    
    class LoginAuth(BaseAuthentication):
        # 重写authenticate方法
        def authenticate(self, request):
            token = request.query_params.get('token')
            ret, user_info = check_token(token)
            if ret:
                return user_info, None
            # 如果查不到,抛异常
            raise exceptions.APIException('您认证失败')
    def create_token(user_id):
        md5 = hashlib.md5()
        md5.update(user_id.encode('utf-8'))
        # 加盐加密
        md5.update(settings.password.encode('utf-8'))
        hex = md5.hexdigest()
        # 加密完以后,直接在后面拼上id,用于认证,传过来的id加密以后是否和原token一致
        token = hex + '|' + user_id
        print(token)
        return token
    
    
    # 登录
    # 产生随机字符串的时候就不需要存到数据库中
    class Login(APIView):
        authentication_classes = []
    
        def post(self, request, *args, **kwargs):
            response = {'status': 100, 'msg': '登录成功'}
            name = request.data.get('name')
            pwd = request.data.get('pwd')
            try:
                user = models.UserInfo.objects.get(name=name, pwd=pwd)
                user_info_json = json.dumps({'name': user.name, 'id': user.pk})
                # 生产dafgasdewf|{'name':user.name,'id':user.pk}的token
                token = create_token(str(user.pk))
                response['token'] = token
            except ObjectDoesNotExist as e:
                response['status'] = 101
                response['msg'] = '用户名或密码错误'
            except Exception as e:
                response['status'] = 102
                # response['msg']='未知错误'
                response['msg'] = str(e)
            return JsonResponse(response, safe=False)

    四、源码分析

    as_view ----------> view -------------> dispatch -------> Request包装新的request ------> 认证、权限、频率 --------> 根据请求方式分发到不同的方法

    url(r'books/',views.Book.as_view())

    1、Book中没有as_view

    2、APIView的as_view

    class APIView(View):
        
        @classmethod
        # cls 是 Book类
        def as_view(cls, **initkwargs):
                
            # view = super(APIView, Book).as_view(**initkwargs)
            view = super(APIView, cls).as_view(**initkwargs)
            view.cls = cls
            view.initkwargs = initkwargs
    
            # Note: session based authentication is explicitly CSRF validated,
            # all other authentication is CSRF exempt.
            return csrf_exempt(view)

    3、view = super(APIView, cls).as_view(**initkwargs) ---------------------> View中的as_view

    class View(object):
        
        @classonlymethod
        # cls====> Book
        def as_view(cls, **initkwargs):
    
            def view(request, *args, **kwargs):
                # 实例化产生一个book对象
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                # 调dispatch方法
                return self.dispatch(request, *args, **kwargs)
            view.view_class = cls
            view.view_initkwargs = initkwargs
    
            # take name and docstring from class
            update_wrapper(view, cls, updated=())
    
            # and possible attributes set by decorators
            # like csrf_exempt from dispatch
            update_wrapper(view, cls.dispatch, assigned=())
            return view

    4、return self.dispatch(request, *args, **kwargs) ----------------> dispatch

    self====> Book对象,一层层找dispatch

    APIView中找到dispatch

    class APIView(View):
        
        def dispatch(self, request, *args, **kwargs):
    
            self.args = args
            self.kwargs = kwargs
            
            # (a)初始化request,就是通过Request类来包装原生request,得到包装后的request
            request = self.initialize_request(request, *args, **kwargs)
            # 从现在开始request就是包装后的request
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                # (b) 认证、权限、频率
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                # http_method_names表示列表['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
                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

    (a)request = self.initialize_request(request, *args, **kwargs) 包装 request

    self 是Book对象

    class APIView(View):
        # 默认的认证列表类
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        def initialize_request(self, request, *args, **kwargs):
            """
            Returns the initial request object.
            """
            parser_context = self.get_parser_context(request)
            # (a-b)实例化初始化产生新的request对象
            return Request(
                request,
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(),  # 认证类实例化产生的对象的列表
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
        def get_authenticators(self):
            """
            Instantiates and returns the list of authenticators that this view can use.
            """
            return [auth() for auth in self.authentication_classes]
    (a------1)return Request( ··· ) ----------> Request类初始化
        def __init__(self, request, parsers=None, authenticators=None,
                     negotiator=None, parser_context=None):
            assert isinstance(request, HttpRequest), (
                'The `request` argument must be an instance of '
                '`django.http.HttpRequest`, not `{}.{}`.'
                .format(request.__class__.__module__, request.__class__.__name__)
            )
    
            self._request = request
            self.parsers = parsers or ()
            self.authenticators = authenticators or ()
            self.negotiator = negotiator or self._default_negotiator()
            self.parser_context = parser_context
            self._data = Empty
            self._files = Empty
            self._full_data = Empty
            self._content_type = Empty
            self._stream = Empty
    
            if self.parser_context is None:
                self.parser_context = {}
            self.parser_context['request'] = self
            self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
    
            force_user = getattr(request, '_force_auth_user', None)
            force_token = getattr(request, '_force_auth_token', None)
            if force_user is not None or force_token is not None:
                forced_auth = ForcedAuthentication(force_user, force_token)
                self.authenticators = (forced_auth,)

    (b)self.initial(request, *args, **kwargs) -----> 认证、权限、频率

      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
            # (b------1) 认证
            self.perform_authentication(request)
            # (b------2)权限
            self.check_permissions(request)
            # 频率
            self.check_throttles(request)
    (b------1) self.perform_authentication(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

    (b------1------1) 调用request的user方法,request是Request实例化产生的对象

    class Request(object):
        @property
        def user(self):
            
            # 这里的self是request
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()   # 具体方法如下
            return self._user

    (b------1------1-------1) self._authenticate()

    class Request(object): 
        def _authenticate(self):
            # 这里的self是request
            # self.authenticators就是在Request实例化的时候,传过来的认证类的对象的列表
            # 即:[auth() for auth in self.authentication_classes]
            for authenticator in self.authenticators:
                try:
                    # 重写的就是这个authenticate()方法,
                    # 两个参数,第一个是对象本身(自动传);第二个是这里的self,就是request
                    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()
  • 相关阅读:
    コナン純黒のナイトメア20180715
    コナン純黒のナイトメア20180630
    コナン純黒のナイトメア20180629
    コナン純黒のナイトメア20180623
    コナン純黒のナイトメア20180622
    コナン純黒のナイトメア20180616
    コナン純黒のナイトメア20180613
    コナン純黒のナイトメア20180611
    单词乱记 20180607
    五周突破日语能力考试 单词5-2
  • 原文地址:https://www.cnblogs.com/zhangbingsheng/p/10720954.html
Copyright © 2011-2022 走看看