• django 之csrf、auth模块及settings源码、插拔式设计


    csrf:Cross Site Request Forgery protection

    基于django中间件拷贝思想

    # start.py
    import notify
    notify.send_all('小宝贝们!快要放假啦,你们都去哪里玩呀?')
    
    # settings.py
    NOTIFY_LIST = [
        'notify.email.Email',
        'notify.msg.Msg',
        'notify.wechat.WeChat'
    ]
    
    
    # notify文件夹下(start.py和.settings.py和notify文件夹在同一目录下)
    # __init__.py
    # coding:utf8
    
    import settings
    import importlib
    def send_all(content):
        for path in settings.NOTIFY_LIST:
            file_path,cls_name = path.rsplit('.',1)
            file_name = importlib.import_module(file_path)
            cls = getattr(file_name,cls_name)
            obj = cls()
            obj.send(content)
            
    # email.py
    # coding:utf8
    class Email:
        def __init__(self):
            pass
        def send(self,content):
            print('邮件通知:%s'%content)
    
    # msg.py
    # coding:utf8
    class Msg:
        def __init__(self):
            pass
        def send(self,content):
            print('短信通知:%s'%content)
     
    # wechat.py
    # coding:utf8
    class WeChat:
        def __init__(self):
            pass
        def send(self,content):
            print('微信通知:%s'%content)
    
    

    跨站请求伪造简介

    钓鱼网站
    			你自己写一个跟中国银行正规网站一模一样的页面
    			用户输入用户名 密码 对方账户  转账金额提交
    			请求确实是朝中国银行的接口发送的 钱也扣了
    			但是对方账户变了 变成了钓鱼网站自己提前设置好的账户
    			
    			如何实现
    				你在写form表单的时候 让用户填写的对方账户input并没有name属性
    				而是你自己在内部偷偷隐藏了一个具有name属性的input框
    				并且value值是你自己的账户 然后将该标签隐藏了
    				
    			模拟该现象的产生
    				创建两个django项目
    

    ps:

    def transfer(request):
        if request.method =='POST':
            username = request.POST.get('username')
            target_user = request.POST.get('target_user')
            money = request.POST.get('money')
            print('%s给%s转账了%s元钱!'%(username,target_user,money))
        return render(request,'transfer.html')
    
    <!--正经网站-->
    <body>
    <p>这是legal_site</p>
    <form action="" method="post">
        <p>username:<input type="text" name="username"></p>
        <p>target_account:<input type="text" name="target_user"></p>
        <p>money:<input type="text" name ="money"></p>
        <input type="submit">
    </form>
    </body>
    <!--钓鱼网站-->
    
    <body>
    <p>这是fake_site</p>
        <!--不指定路径,默认往当前网站提交,所以这里要伪造成往真正的网站站点提交,真网站后台获取的是钓鱼网站隐藏的用户名的input框指定的value属性值(默认值,就是骗子账户),那么之后转账的处理功能可定就是往这个骗子账户转钱了-->
    <form action="http://127.0.0.1:8000/transfer/" method="post">
        <p>username:<input type="text" name="username"></p>
        <!--吧用户转给真正目标用户的input框的name属性删除了,自己再多加一个input框添加上name属性,并设上value(默认值)属性(骗子的账户名),并把该标签隐藏,后端获取name属性的username,就是骗子设置value属性的骗子账户-->
        <p>target_account:<input type="text" >
            <input type="text" name="target_user"  value="zhang" style="display:none">
        </p>
        <p>money:<input type="text" name ="money"></p>
        <input type="submit">
    </form>
    </body>
    

    跨站请求伪造解决思路

    
    如何解决该问题:
    	只处理本网站发送的post请求
    
    如何识别如何判断当前请求是否是本网张发出的
    
        解决:
       网站在返回给用户一个form表单的时候 会自动在该表单隐藏一个input框
    这个框的value是一个随机字符串 但是网站能够记住给每一个浏览器发送的随机字符串,并与自己后台保存的随机字符串比较,两者一致则通过,否则就返回不存在(403),不再对该请求做处理;
    

    方式1:form表单发post请求解决方法

    在写form表单的时候 只需要在表单中写一个{% csrf_token %}
    <form action="" method="post">
        {% csrf_token %}
        <p>username:<input type="text" name="username"></p>
        <p>target_account:<input type="text" name="target_user"></p>
        <p>money:<input type="text" name="money"></p>
        <input type="submit">
    </form>
    

    下图是网站在form表单隐藏的随机字符串,那么属性就是settings文件中的django自带的七个中间件中的一个,以后用form表单发送post请求时,表单内写上{% csrf_token %},就不需要注释settings中的中间件django.middleware.csrf.CsrfViewMiddleware了。

    方式2:ajax发post请求解决方法

    ajax方式2:

    较为繁琐

    ​ 先在页面任意的位置上书写{% csrf_token %},然后在发送ajax请求的时候 通过标签查找获取随机字符串添加到data自定义对象即可。

    <!--ajax方式1:-->
    
    {% csrf_token %}
    <button id="d1">发送ajax</button>
    <script>
        $('#d1').click(function () {
            $.ajax({
                url:'',
                type:'post',
                data:{'username':'zhang','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
                success:function (data) {
                    
                }
            })
        })
    </script>
    

    ajax方式2:

    较为简单

    在ajax方式1的基础上,将ajax内部参数data改成如下方式,这样就不需要在任意位置写{{% csrf_token %}}了,
    因为data中的模板语法{{csrf_token}}就相当于一个变量,帮我们生成一个随机字符串。

    <!--ajax方式2:-->
    data:{'username':'zhang','csrfmiddlewaretoken':'{{ csrf_token }}'}
    

    ajax方式3:

    最通用的一种方式

    官网提供的文件,直接兴建js文件拷贝代码 导入即可
    你不需要做任何的csrf相关的代码书写

    第一步:先在settings文件中配置static路径:

    STATIC_URL = '/static/'
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR,'static')
    ]
    

    第二部:建立static文件夹,自内部建一个js文件,将如下代码拷进去;

    // setup.js文件
    
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');
    
    
    function csrfSafeMethod(method) {
      // these HTTP methods do not require CSRF protection
      return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    
    $.ajaxSetup({
      beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
      }
    });
    
    

    第三步:在html页面上通过导入该文件即可自动帮我们解决ajax提交post数据时校验csrf_token的问题,(导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    </head>
    <body>
        <button id="d1">发送ajax</button>
        
    <!--导入该js文件-->
    <script src="/static/setup.js"></script>
    
    <script>
        $('#d1').click(function () {
            $.ajax({
                url:'',
                type:'post',
                data:{'username':'zhang'},
                success:function (data) {
                    alert(data)
                }
            })
        })
    </script>
    </body>
    </html>
    

    scrf详见===>Djagno官方文档中关于CSRF的内容](https://docs.djangoproject.com/en/1.11/ref/csrf/)

    csrf相关的两个装饰器

    将settings中csrf中间件注释掉,代表所有的视图函数在浏览器的请求都不校验;

    注释掉csrf中间件的情况下,如果想对某个post请求的视图函数校验,可以加装饰器@csrf_protect

    没注释掉csrf中间件的情况下,如果朝某个视图函数发送post请求,排除校验,可以加装饰器@csrf_exempt

    这两个装饰器跟settings中的csrf中间配置没有关系,它内部源码自动调用csrf。

    	from django.shortcuts import render,HttpResponse
        from django.views.decorators.csrf import csrf_exempt,csrf_protect
        # @csrf_exempt  # 不校验 csrf
        def index(request):
            return HttpResponse('index')
    
        @csrf_protect  # 校验
        def login(request):
            return HttpResponse('login')			
    

    csrf装饰器在CBV上的特例

    csrf_exempt和csrf_protect装饰器在CBV上的不同:

    ​ csrf_exempt:这个装饰器只能给dispatch装才能生效

    ​ csrf_protect:全都可以,跟普通的装饰器装饰CBV一致

    ps:csrf_exempt

    # @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持该方法
    @method_decorator(csrf_exempt,name='dispatch')  # 意思是给其类内部名为dispatch的函数装装饰器,支持该方法。
    class MyIndex(views.View):
        # @method_decorator(csrf_exempt)  # 可以
        def dispatch(self, request, *args, **kwargs):
            return super().dispatch(request,*args,**kwargs)
        def get(self,request):
            return render(request,'transfer.html')
        # @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持该方法
        def post(self,request):
            return HttpResponse('OK')		
    

    ps:csrf_protect

    # @method_decorator(csrf_protect,name='post')  # 可以
    class MyIndex(views.View):
        @method_decorator(csrf_protect)
        def dispatch(self, request, *args, **kwargs):
            return super().dispatch(request,*args,**kwargs)
        def get(self,request):
            return render(request,'transfer.html')
        # @method_decorator(csrf_protect)  # 可以
        def post(self,request):
            return HttpResponse('OK')
    

    django settings源码

    django有两个配置文件
    一个是暴露给用户可以配置的

    ​ 一个是内部全局的(用户配置了就用用户的 用户没有配就用自己的)

    from django.conf import global_settings
    from django.conf import settings
    
    obj.name = 'zhang'  # 全局
    obj.name = 'li'  # 局部
    		
    先加载全局配置 给对象设置
    然后在加载局部配置  再给对象设置
    一旦有重复的项  后者覆盖前者
    
    class Settings(object):
        def __init__(self, settings_module):  # 项目名:settings
            # update this dict from global settings (but only for ALL_CAPS settings)
            for setting in dir(global_settings):  # 获取django全局配置文件中所有的变量名
                if setting.isupper():  # 只有大写的才能通过
                    setattr(self, setting, getattr(global_settings, setting))
                    # self就是settings
            # store the settings module in case someone later cares
            self.SETTINGS_MODULE = settings_module
    
            mod = importlib.import_module(self.SETTINGS_MODULE)
            # from 项目名 import settings 导入暴露给用户的配置文件
            # mod就是模块settings
    
            tuple_settings = (
                "INSTALLED_APPS",
                "TEMPLATE_DIRS",
                "LOCALE_PATHS",
            )
            self._explicit_settings = set()
            for setting in dir(mod):  # 获取暴露给用户的配置文件中所有的变量名
                if setting.isupper():  # 变量名必须是大写
                    setting_value = getattr(mod, setting)  # 利用反射获取大写变量名对应的值
    
                    if (setting in tuple_settings and
                            not isinstance(setting_value, (list, tuple))):
                        raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                    setattr(self, setting, setting_value)  # 
                    self._explicit_settings.add(setting)
    
            if not self.SECRET_KEY:
                raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
    
            if hasattr(time, 'tzset') and self.TIME_ZONE:
                # When we can, attempt to validate the timezone. If we can't find
                # this file, no check happens and it's harmless.
                zoneinfo_root = '/usr/share/zoneinfo'
                if (os.path.exists(zoneinfo_root) and not
                        os.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))):
                    raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
                # Move the time zone info into os.environ. See ticket #2315 for why
                # we don't do this unconditionally (breaks Windows).
                os.environ['TZ'] = self.TIME_ZONE
                time.tzset()
    
        def is_overridden(self, setting):
            return setting in self._explicit_settings
    
        def __repr__(self):
            return '<%(cls)s "%(settings_module)s">' % {
                'cls': self.__class__.__name__,
                'settings_module': self.SETTINGS_MODULE,
            }
    

    auth模块简介

    Auth模块使Django自带的用户认证模块,它内置了强大的用户认证系统--auth,默认使用auth-user表来存储用户数据;这样开发人员就不用手写django-ORM部分的登录、注册,用户认证、注销、修改密码等功能,以及创建user表的麻烦。

    首先,执行数据库迁移命令,找到auth-user这张表,内部有:password、last_login、is_superuser first_name last_name email is_staff is_active date_joined username 字段。

    然后创建按超级用户:

    ​ python3 manage.py createsuperuser # cmd执行该命令

    ​ 需要输入创建超级用户的用户名和密码以及再次创建密码

    auth创建用户

    1.创建用户

    User.objects.create(username=username,password=password)  # 不可用  密码不是加密的
    User.objects.create_user(username=username,password=password)  # 创建普通用户    密码自动加密
     # 创建超级用户   需要邮箱数据,密码自动加密
    User.objects.create_superuser(username=username,password=password,email='123@qq.com') 
    
    from django.contrib.auth.models import User
    def register(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            # User.objects.create(username=username,password=password)
            # User.objects.create_user(username=username,password=password)
            User.objects.create_superuser(username=username,password=password,email='111@qq.com')
            return HttpResponse('注册成功!!')
    
        return render(request,'register.html')
    
    

    2.校验用户名和密码是否正确

    from django.contrib import auth
    # 必须传用户名和密码两个参数缺一不能
    user_obj = auth.authenticate(request,username=username,password=password)	
    

    3.保存用户登录状态

    auth.login(request,user_obj)
    # 只要这句话执行了 后面在任意位置 只要你能拿到request你就可以通过request.user获取到当前登录的用户对象
    
    def login_dunc(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            # 校验当前用户名和密码是否正确
            # models.User.objects.filter(username=username,password=password)
            # res = User.objects.filter(username=username,password=password)  # 密码无法校验,前端传的是明文,后端存的是密文
            # print(res)
            res = auth.authenticate(request,username=username,password=password)  # 自动加密密码 然后去数据库校验
            # print(res)  # 数据对象
            # print(res.username)
            # print(res.password)  # 密码是密文
            if res:
                # 保存用户登录状态
                # request.session['user'] = 'zhang'
                auth.login(request,res)
                """
                只要执行了这一句话 之后你可以在任意位置
                通过request.user获取到当前登录用户对象
                """
                return HttpResponse('ok')
        return render(request,'login_dunc.html')
    

    4.判断当前用户是否登录

    request.user.is_authenticated()
    
    def get_user(request):
        print(request.user)  # AnonymousUser  匿名用户
        print(type(request.user))  # <class'django.utils.functional'>
        print(request.user.is_authenticated())  # 判断当前用户是否登录,返回布尔值
        return HttpResponse('ok')
    

    5.校验原密码是否正确

    request.user.check_password(old_password)
    

    6.修改密码

    request.user.set_password(new_password)
    request.user.save() # 修改密码一定要保存
    
    # @login_required(login_url='/lgg/')  # 局部配置,如没有登录,让用户直接跳到登录页面
    @login_required
    def check_password(request):
        if request.method == "POST":
            old_password = request.POST.get('old_password')
            new_password = request.POST.get('new_password')
            # 校验老密码是否正确
            is_right = request.user.check_password(old_password)
            if is_right:
                request.user.set_password(new_password)
                request.user.save()  # 修改密码一定要保存
        user_obj = request.user
        return render(request,'change_password.html',locals())
    

    7.注销

    auth.logout(request)
    
    @login_required
    def logout(request):
        # request.session.flush()
        auth.logout(request)
        return HttpResponse('注销了')
    

    8.校验用户是否登录装饰器

    局部配置

    from django.contrib.auth.decorators import login_required
    @login_required(login_url='/login/')  # 局部配置
    def index(request):
        pass
    

    全局配置

    settings配置文件中 直接配置
    LOGIN_URL = '/login/'
    
    @login_required
    def index(request):
        pass
    # 如果全局配置了 局部也配置  以局部的为准
    

    auth扩展表

    如何扩展auth_user表字段?

    方式1
    利用一对一外键字段关系

    class UserDetail(models.Model):
        phone = models.BigIntegerField()
        user = models.OneToOneField(to='User')
    

    方式2:

    ​ 利用继承关系

    首先先去配置文件settings中配置
    AUTH_USER_MODEL = 'app01.Userinfo'  # 应用名.表名
    之后所有的auth模块功能全都以你写的表为准,由于继承了AbstractUser,会将auth_user那张表与自己建的这张表整合到一起,auth_user本身所拥有的功能,userinfo也能照常使用,使表的操作性更强了。			
    
    # 表字段写好之后,记得数据库迁移
    from django.contrib.auth.models import AbstractUser
    class Userinfo(AbstractUser):
        phone = models.Bi  gIntegerField()
        register_time = models.DateField(auto_now_add=True)
    

    基于django settings配置文件实现插拔式设计

    conf文件夹、lib文件夹、start.py处于同级

    # conf文件夹
    # settings.py
    NAME = '暴露给用户自定义配置'
    
    #lib>conf文件夹
    
    
    # __init__.py文件
    import importlib
    from lib.conf import global_settings
    import os
    class Settings(object):
        def __init__(self):
            # print(111111)
            for name in dir(global_settings):
                if name.isupper():
                    setattr(self,name,getattr(global_settings,name))
            # 获取暴露给用户的配置文件字符串路径
            module_path = os.environ.get('xxx')
            md = importlib.import_module(module_path)  # md = settings
            for name in dir(md):
                if name.isupper():
                    k = name
                    v = getattr(md,name)
                    setattr(self,k,v)
    settings = Settings()
    
    # global_settings.py文件
    NAME = '项目默认的配置文件'
    
    
    # start.py文件
    import os
    import sys
    
    BASE_DIR = os.path.dirname(__file__)
    sys.path.append(BASE_DIR)
    if __name__ == '__main__':
        os.environ.setdefault('xxx','conf.settings')
        from lib.conf import settings
        print(settings.NAME)  # settings就是类Settings的对象(单例)
    
  • 相关阅读:
    自制凉皮
    牛人
    史记
    阅读detection
    最近的购书清单
    不要轻易挑战用户的习惯,否则会被用户打脸!
    INVEST原则的应用
    谈谈Backlog梳理活动
    要写封闭式的用户故事
    敏捷教练的八种失败角色
  • 原文地址:https://www.cnblogs.com/zhangchaocoming/p/11993039.html
Copyright © 2020-2023  润新知