zoukankan      html  css  js  c++  java
  • 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的对象(单例)
    
  • 相关阅读:
    【leetcode】1020. Partition Array Into Three Parts With Equal Sum
    【leetcode】572. Subtree of Another Tree
    【leetcode】123. Best Time to Buy and Sell Stock III
    【leetcode】309. Best Time to Buy and Sell Stock with Cooldown
    【leetcode】714. Best Time to Buy and Sell Stock with Transaction Fee
    【leetcode】467. Unique Substrings in Wraparound String
    【leetcode】823. Binary Trees With Factors
    【leetcode】143. Reorder List
    【leetcode】1014. Capacity To Ship Packages Within D Days
    【leetcode】1013. Pairs of Songs With Total Durations Divisible by 60
  • 原文地址:https://www.cnblogs.com/zhangchaocoming/p/11993039.html
Copyright © 2011-2022 走看看