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()

原文地址:https://www.cnblogs.com/zhangbingsheng/p/10720954.html

时间: 2024-11-15 06:01:33

Django框架(十九)--Django rest_framework-认证组件的相关文章

Django 框架篇(九): Django中的Form 组件

Django中的Form组件,帮我们处理了 form表单中的校验, 和错误提示的处理: 主要功能: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容 使用form组件实现注册功能 1.  定义一个类, 继承django中的 forms.Form 代码实例: from django import forms # 按照Django form组件的要求自己写一个类 class RegForm(forms.Form): name = forms.CharField(label="用

Django之REST_FRAMEWORK 认证组件

Django之DRF之认证组件 # from rest_framework.views import APIView # APIView 中的 dispatch 中 执行的 self.initial(request,*args,**kwargs)中的 # APIView---->dispatch------>self.initial------>三个校验 # self.perform_authentication(request) # 认证校验 # self.check_permissi

Django:(6)用户认证组件 & 中间件

用户认证组件 用户认证组件: 功能:用session记录登陆验证状态 前提:用户表:django自带的auth_user 创建超级用户的命令: python manage.py createsuperuser API: (1)from django.contrib import auth (auth模块的方法) 1. # 做验证:如果 验证成功 返回 user 对象(就是 auth_user 这张表中的一条记录对象),否则返回None user = auth.authenticate(usern

12.Django基础十之Form和ModelForm组件

一 Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确.如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.. Django form组件就实现了上面所述的功能. 总结一下,其实form组件的主要功能如下: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容 普通方式手写注册功能

Django基础十之Form和ModelForm组件

一 Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确.如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.. Django form组件就实现了上面所述的功能. 总结一下,其实form组件的主要功能如下: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容 普通方式手写注册功能

Django框架进阶7 django请求生命周期流程图, django中间件, csrf跨站请求伪造, auth认证模块

django请求生命周期流程图 djang中间件 它是django的门户 只要是全局相关的功能你都应该考虑使用django中间件来帮你完成 全局用户身份校验 全局用户访问频率校验 用户访问黑名单 用户访问白名单 # 只要以后用django开发业务 设计到全局相关的功能 你就考虑用中间件 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.Sessio

Django 框架篇: 一. Django介绍; 二. 安装; 三. 创建项目;

一. web框架:   框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演. 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\

简单Django框架推导及Django安装介绍

小结 软件开发架构 c/s 架构 客户端 服务端 b/s 架构 浏览器 服务端 HTTP协议:超文本传输协议 四大特性 基于TCP/IP之上作用于应用层 基于请求响应 无状态(不保存状态,每次请求都认为是陌生的请求) 无连接 (一旦数据交互完成就断开连接) 对比:长连接(websocket(HTTP协议大补丁) 数据格式 请求格式: ? 请求首行(请求方式,协议版本......) ? 请求头(一大堆k:v键值对) ? \r\n ? 请求体(真正的数据 ,发post请求的时候才有,如果是get请求

Django框架(九)--cookie和session

cookie和session组件 cookie 1.cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况. 一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的. 状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留.会话中产生的数据又是我们需要保存的,也就是说要“保持状态”.因此C

python 学习笔记十九 django项目bbs论坛

跨站请求伪造保护 CSRF 中间件和模板标签提供对跨站请求伪造简单易用的防护.某些恶意网站上包含链接.表单按钮或者JavaScript ,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站攻击.还有另外一种相关的攻击叫做“登录CSRF”,攻击站点触发用户浏览器用其它人的认证信息登录到其它站点. 全局: 中间件 django.middleware.csrf.CsrfViewMiddleware 局部: @csrf_protect,为当前函数强制设置防跨站请求伪造功能