zoukankan      html  css  js  c++  java
  • 高级配置文件, csrf, django settings源码, django auth模块, 文件配置的插拔式设计

    基于jiango中间件思想实现文件配置

    通知功能: 邮件, 短信, 微信

    importlib模块

    import importlib
    
    res = 'lib_test.test'
    
    # 正常导入模块
    # from lib_test import test
    # print(test)  # <module 'lib_test.test' from '...'>
    
    # 利用字符串的形式导入模块, 该字符串最小单位到文件名, 不能到文件里的变量名
    md = importlib.import_module(res)
    print(md)  # <module 'lib_test.test' from '...'>
    

    仿照django中间件的文件配置方式

    notify(Package): init.py, email.py, msg.py, wechat.py

    之后新加功能或是删除功能只需要在NOTIFY_LIST中操作对应的字符串即可

    '''
    settings.py:
    NOTIFY_LIST = [
    	'notify.email.Email',
        'notify.msg.Msg',
        'notify.wechat.WeChat',
    ]
    
    
    __init__.py:
    import settings
    import importlib
    
    
    def send_all(content):
        for path_name in settings.NOTIFY_LIST:
            md_name, cls_name = path_name.rsplit('.', maxsplit=1)  # 从右开始以"."开始切割字符串, 只切割一次
            md = importlib.import_module(md_name)  # 以email为例, 通过字符串导入notify.email
            cls = getattr(md, cls_name)  # 通过类名获取notify.email中的Email
            obj = cls()
            obj.send(content)
    
    
    start.py:
    from notify import *
    
    send_all('开始放假啦!')
    '''
    

    跨站请求伪造

    钓鱼网站

    1. 自己写一个根中国银行正规网站一模一样的页面
    2. 用户输入用户名, 密码, 转账账户, 转账金额并提交
    3. 请求确实是朝中国银行的接口发送的, 但是转账账户变成了钓鱼网站提前设置的账户
    4. 钓鱼网站的form表单中让用户填写转账账户的input框剔除了name属性,
    5. 并且隐藏了一个input框, 其value值为提前设置的钓鱼账户并且还具有对应的name属性

    模拟该现象的产生

    创建两个django项目, 一个为正规网站的服务器, 一个为钓鱼网站的服务器

    '''
    dj fake
    
    views.py
    def transfer(request):
        return render(request, 'transfer.html')
          
    transfer.html
    <form action="http://127.0.0.1:8000/transfer/" method="post">  # 将用户输入的信息朝正规网站接口发送
        ...
        <p>
            target_account: <input type="text">
            <input type="text" name="target_account" value="jason" style="display: none">
        </p>
        ...
    </form>
    '''
    

    跨站请求伪造解决方法

    只处理同一个浏览器发出的POST请求,

    关键: 判断post请求是否为同一浏览器发出的

    解决方法:

    • 服务器在返回给用户一个form表单的时候, 会自动在该表单中隐藏一个input框, <input type="hidden" name="csrfmiddlewaretoken" value="...">
    • 该框的value是一个随机的token加密字符串, 该token永不重复, 最重要的是会唯一标识浏览器信息, 并且随每次POST请求动态变化

    CsrfViewMiddleware: 校验浏览器POST请求中csrfmiddlewaretoken的中间件

    • {% csrf_token %}, 浏览器在渲染HTML模板时会在对应位置做处理

    ajax请求添加csrf校验

    csrf校验机制: 在提交的post请求数据中校验是否有 key为csrfmiddlewaretoken, value为token加密字符串的数据

    方式一:

    先在页面任意非form表单位置书写{% csrf_token %}, 在发送ajax请求时, 通过标签查找获取token键值对添加到ajax的data参数中

    '''
    ...
                $.ajax({
                    url: '',
                    type: 'post',
                    data: {'username': 'jason', 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                    success: function (data) {
                        alert(data)
                    }
                })
    ...
    </script>
    '''
    

    方式二:

    1. 通用方式, 新建setup.js文件拷贝官网提供的js代码,
    2. 然后在HTML模板页面任意位置: 1. 使用script标签的src参数导入该setup.js文件, 2. 不需要在该页面再写任何csrf相关的代码
    3. <script src="{% static 'setup.js' %}"></script>

    csrf相关的两个装饰器

    在FBV上装饰的情况

    from django.views.decorators.csrf import csrf_exempt, csrf_protect
    
    
    @csrf_exempt  # 装饰器, 不进行csrf校验
    def index(request):
        return HttpResponse('index')
    
    
    @csrf_protect  # 装饰器, 进行csrf校验
    def login(request):
        return HttpResponse('login')
    

    在CBV上装饰的情况

    csrf_protect装饰器对于cbv的三种装饰方式都适用

    csrf_exempt装饰器只能给dispatch函数装才能生效

    from django import views
    from django.utils.decorators import method_decorator
    
    
    # @method_decorator(csrf_exempt, name='post')  # 不支持
    class MyIndex(views.View):
        @method_decorator(csrf_exempt, name='dispatch')
        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')  # 不支持
        def post(self, request):
            return HttpResponse('OK了')
    
    '''
    urls.py: 
    	url(r'^cbv/', views.MyIndex.as_view())
    	
    transfer.html: 
    	<form action="/cbv/" method="post">
    '''
    

    django settings源码

    django有两个配置文件:

    • 一个是内部全局的, 一个是展示给用户可以自定义配置的,
    • 用户配置了就使用用户的, 用户没有配置就使用内部全局的

    实现原理:

    • 先加载全局配置, 给对象设置,
    • 然后加载局部配置, 再给对象设置,
    • 一旦有重复的项, 后者覆盖前者
    '''
    from django.conf import settings
    from real import settings
    
    1. settings = LazySettings(), 基于模块的单例模式, settings为一个对象
    2. os.environ是一个内部全局的大字典,
    3. manage.py: os.environ.setdefault("DJANGO_SETTINGS_MODULE", "real.settings"), 给内部全局的大字典设置键值对
    4. class LazySettings所在文件中的全局变量: ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE",
    5. class LazySettings:  
            settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
            settings_module为展示给用户的配置文件的路径, '项目名.settings'
            self._wrapped = Settings(settings_module)
    6. class Settings:
            for setting in dir(global_settings): 获取django内部全局的settings中所有的变量名
                if setting.isupper():  # 变量名必须大写
                    setattr(self, setting, getattr(global_settings, setting))  
                    # self: settings._wrapped对象(组合), 给settings._wrapped对象设置内部全局的settings中的键值对
            
            self.SETTINGS_MODULE = settings_module  # '项目名.settings'
            mod = importlib.import_module(self.SETTINGS_MODULE)  # from 项目名 import settings, mod指向展示给用户的配置文件
            
            for setting in dir(mod):  # 获取展示给用户的配置文件中所有的变量名
                if setting.isupper():  # 变量名必须是大写
                    setting_value = getattr(mod, setting)  # 通过反射获取大写变量名所对应的值
                    setattr(self, setting, setting_value)  # 给settings._wrapped对象设置展示给用户的settings中的键值对
    '''
    

    django auth模块

    创建用户

    创建超级用户(root): python manage.py createsuperuser

    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='123@qq.com')  # 创建超级用户, 必须要有邮箱名
            return HttpResponse('注册成功!')
    
        return render(request, 'register.html')
    

    校验用户

    from django.contrib import auth
    
    
    def log_in(request):
        if request.method == 'POST':
            username = request.POST.get('username')
            password = request.POST.get('password')
            # res = User.objects.filter(username=username, password=password)
            # print(res)  # <QuerySet []>, 表中密码为密文, 无法直接校验
    
            res = auth.authenticate(request, username=username, password=password)  # 自动加密密码, 然后去数据库校验, 必须传两个参数
            # print(res)  # Dragon, 内部封装好了__str__方法, res实际是一个对象
            # print(res.username)  # Dragon
            # print(res.password)  # pbk..., 密码对应的密文
    
            if res:
                # 保存登录状态, 之前自己保存: request.session['user'] = res.username
                auth.login(request, res)  # 执行后可以在后端任意位置通过request.user获取当前登录用户对象
    
                return HttpResponse('登录成功!')
    
        return render(request, 'log_in.html')
    
    
    def get_user(request):
        print(request.user, type(request.user))
        # Dragon, <class 'django.utils.functional.SimpleLazyObject'>, 未登录: AnonymousUser, 匿名用户
    
        print(request.user.is_authenticated())  # True/False, 判断当前用户是否登录
        return HttpResponse('OK!')
    

    修改密码

    from django.contrib.auth.decorators import login_required  # 导入登录认证装饰器
    
    
    # @login_required(login_url='/log_in/')  # 局部配置登录认证装饰器, login_url参数为用户未登录时的跳转页面
    @login_required  # 全局配置登录认证装饰器: settings.py-->LOGIN_URL = '/log_in/', 如果局部和全局都配置了以局部为准
    def change_password(request):
        user_obj = request.user  # 经过登录认证装饰器后, user_obj肯定不为空
    
        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)  # check_password: 校验密码
            if is_right:
                request.user.set_password(new_password)  # set_password: 设置新密码
                request.user.save()  # 修改后需要保存
    
        return render(request, 'change_password.html', locals())
    

    注销

    会删除django_session表中的对应记录

    @login_required
    def logout(request):
        # 自己删: request.session.flush()
        auth.logout(request)
        return HttpResponse('注销成功!')
    

    auth模块扩展表字段

    from django.db import models
    from django.contrib.auth.models import User, AbstractUser
    
    
    # 方式一: 通过外键字段做一对一扩展
    class UserDetail(models.Model):
        phone = models.BigIntegerField()
        user = models.OneToOneField(to='User')
    
    
    # 方式二: 通过继承 + settings.py-->AUTH_USER_MODEL = 'app01.UserInfo'  # 应用名.表名(类名) 扩展
    class UserInfo(AbstractUser):
        phone = models.BigIntegerField()  # 扩展字段不能和原auth_user表中的原字段重复
        register_time = models.DateField(auto_now_add=True)
    
    # 方式二扩展字段后, auth模块所有功能仍可以使用, 并且以自定义的表为准
    

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

    '''
    目录: 
        about_django_settings(python项目)
            conf
                settings.py
            lib
                conf
                    __init__.py
                    global_setings.py
    		start.py          
    '''
    
    # settings.py: NAME = '展示给用户的自定义配置'
    
    # global_settings.py: NAME = '项目默认的配置文件'
    
    # __init__.py
    import importlib
    from lib.conf import global_settings
    import os
    
    
    class Settings(object):
        def __init__(self):
            for setting in dir(global_settings):
                if setting.isupper():
                    setattr(self, setting, getattr(global_settings, setting))
    
            module_path = os.environ.get('SETTINGS_MODULE')
            md = importlib.import_module(module_path)  # md == settings
            for setting in dir(md):
                if setting.isupper():
                    setattr(self, setting, getattr(md, setting))
    
    
    settings = Settings()
    
    # start.py
    import os
    import sys
    
    BASE_DIR = os.path.dirname(__file__)
    
    # 把当前项目的根目录添加到环境变量, 使导入语句不会报错, 使用pycharm会自动添加
    sys.path.append(BASE_DIR)
    
    if __name__ == '__main__':
        # os.environ.setdefault('SETTINGS_MODULE', 'conf.settings')
        os.environ['SETTINGS_MODULE'] = 'conf.settings'
    
        from lib.conf import settings
    
        print(settings.NAME)
    
  • 相关阅读:
    集群、分布式、负载均衡区别与联系
    Android--加载大分辨率图片到内存
    Android--MP3播放器MediaPlayer
    Android--SoundPool
    Android--MediaPlayer高级
    Android--调用系统照相机拍照与摄像
    Android--SurfaceView播放视频
    Android--使用VideoView播放视频
    Android--使用Camera拍照
    Vue组件选项props
  • 原文地址:https://www.cnblogs.com/-406454833/p/12012123.html
Copyright © 2011-2022 走看看