基于Django中间件的思想实现功能的配置使用

  • 通知功能

    邮件

    短信

    微信

    qq

    新建一个文件夹,里面包含notify和start文件

    新建一个文件notify

    def send_email(content):
        print('邮箱通知:%s'%content)
    
    def send_msg(content):
        print('短信通知:%s'%content)
    
    def send_wechat(content):
        print('微信通知:%s'%content)
    

    新建一个start文件

    from notify import *
    
    def send_all(content):
        send_msg(content)
        send_email(content)
        send_wechat(content)
    
    if __name__ == '__main__':
        send_all('再坚持一天就周末了')
    

    importlib模块:利用字符串的形式导入模块,该模块字符串最小单位只能到文件名

    新建一个lib文件夹,里面包含aaa,bbb文件

    新建一个aaa文件:

    import importlib
    res = 'lib.bbb'
    # 利用字符串的形式导入模块
    md = importlib.import_module(res)  #等价于 from lib import bbb
    print(md)  # 该模块字符串最小单位只能到文件名
    

    新建一个bbb文件:

    name = 'from bbb'
    

    新建一个包,里面一个文件就是一个功能(邮件、短信、微信、qq)

    利用面向对象的思想,因为django中间件都是类,这里面用到了鸭子类型,这些功能都要发送消息,统一定义一个send方法

    包里面新建一个Email文件:

    class Email(object):
        def __init__(self):
            pass  # 发送邮件需要的前期准备
    
        def send(self,content):
            print('邮件通知:%s'%content)
    

    包里面新建一个msg文件:

    class Msg(object):
        def __init__(self):
            pass  # 发送短信需要的前期准备
    
        def send(self,content):
            print('短信通知:%s'%content)
    
    

    包里面新建一个wechat文件:

    class WeChat(object):
        def __init__(self):
            pass  # 发送微信需要的前期准备
    
        def send(self,content):
            print('微信通知:%s'%content)
    

    包里面新建一个qq文件:

    class Qq(object):
        def __init__(self):
            pass  # 发送qq的前期准备
    
    
        def send(self,content):
            print('qq通知:%s'%content)
    

    在项目文件下文件一个start文件,用来调用里面的功能

    from notify import *  跟包要名字,拿到所有的名字
    
    
    send_all('好嗨哦')
    
    

    再来一个配置文件(settings),模仿django中间件的写法

    NOTIFY_LIST = [
        'notify.email.Email',
        'notify.msg.Msg',
        'notify.wechat.WeChat',
        'notify.qq.Qq',
    ]
    列表中放的字符串就是文件的路径
    

    然后在__init__文件中导入

    import settings
    import importlib
    
    
    def send_all(content):
        for path in settings.NOTIFY_LIST:  # "notify.email.Email"拿到一个个字符串路径
            module_path, cls_name = path.rsplit('.',maxsplit=1)  # module_path = 'notify.email'  cls_name = "Email"
            #module_path是第六行注释(notify.email)字符串, cls_name就是类,也是字符串第六行注释(Email)
            md = importlib.import_module(module_path)  # from notify import email
            cls = getattr(md,cls_name)  #利用反射 获取到文件中类的名字
            obj = cls()  # 实力化产生一个个类的对象
            obj.send(content)
    

跨站请求伪造csrf

钓鱼网站例子

需求:

你自己写一个跟中国银行正规网站一模一样的页面

用户输入用户名 密码 对方账户 转账金额提交

请求确实是朝中国银行的接口发送的 钱也扣了

但是对方账户变了 变成了钓鱼网站自己提前设置好的账户

需要两个后端,就需要两个django项目

如何实现:

你在写form表单的时候 让用户填写的对方账户input并没有name属性

而是你自己在内部偷偷隐藏了一个具有name属性的input框

并且value值是你自己的账户 然后将该标签隐藏了

模拟:

创建两个django项目

如何解决问题:

只处理本网站发送的post请求

如何识别如何判断当前请求是否是本网张发出的

解决:

网站在返回给用户一个form表单的时候 会自动在该表单隐藏一个input框这个框的value是一个随机字符串 但是网站能够记住每一个浏览器发送的随机字符串

新建一个rel(真实的网站项目)和一个fake(伪造的网站项目)

在urls文件中导入模块和设置路由

from app01 import views

url(r'^transfer/',views.transfer),

在views文件中定义transfer函数,由于要发post请求,先去settings文件中把csrf中间件注释掉—>

def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')  #target_user对方账户
        money = request.POST.get('money')
        print('%s 给 %s 转了 %s钱'%(username,target_user,money))
    return render(request,'transfer.html')

—>去templates文件中新建transfer网页,首先添加一个h标签,用来提示区分网页是真实的还是伪造的,

{#<p>这是正儿八经的网站</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>

伪造的网页项目和真实的步骤上面一样,需要在网页中修改p标签,修改端口号,因为不能和真实的端口号一样,如(8001),隐藏标签target_user标签

<p>这是伪造的网站</p>
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_account:<input type="text">
        <input type="text" name="target_user" value="jason" style="display: none">
    </p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">

分别运行起来两个项目,分别输入后缀transfer,可以看到效果

在真实的网站上输入转账是真实到账的,在钓鱼网站上转账,虽然显示转账了,但是给你设置的jason转账了(上面代码第五行),

钓鱼网站的原理是:看上面真实网站的代码第4行和伪造网站的第四五两行就能看出,在name的value值中做了手脚

如何防止钓鱼网站,有什么方法?(真实网站)

如何解决问题:

只处理本网站发送的post请求

如何识别如何判断当前请求是否是本网张发出的

解决思路

解决思路:

网站在返回给用户一个form表单的时候 会自动在该表单隐藏一个input框这个框的value是一个随机字符串 但是网站能够记住每一个浏览器发送的随机字符串到底是什么

在真实的网站上添加一句{% csrf_token %},多了一个隐藏的类型的input框,A每发送一次请求(刷新一次)里面的随机字符串永不重复,这样就知道是谁访问了你的网站,做到了防御钓鱼网站。添加了{% csrf_token %}就可以把settings文件中的csrf注释解开了。

配置文件中的csrf中间件是校验你发送请求的产生的随机字符串,校验用户是否是同一个人。

以后在写form表单时,只要加了{% csrf_token %},配置文件中的csrf中间件就不用注释了。

产生的随机字符串:<input type="hidden" name="csrfmiddlewaretoken" value="rJ47FeK9T55wavvVJGY6UxdM1kTMHhTqotGfaXjXIK8Ahz2Uvs02yR9T8bBn5q2D">

ajax如何解决

{% csrf_token %}
<button id="d1">发送ajax</button>


<script src="/static/setup.js"></script>
<script>
    $('#d1').click(function () 
        $.ajax(
            url:'',
            type:'post',
            // 第一种
            {#data:{'username':'jason','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
            // 第二种
            {#data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
            // 第三种  利用脚本文件
            data:{'username':'jason'},
            success:function (data) {
                alert(data)
            }
        })
    })
</script>

方式1:

先在页面任意的位置上书写{% csrf_token %}
然后在发送ajax请求的时候 通过标签查找获取随机字符串添加到data自定义对象即可
data:{'username':'jason','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},

方式2:

data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},

方式3:

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

新建一个static文件夹,新建一个setup.js文件,里面的代码直接拷贝

自己写一个getCookie方法:
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()方法为ajax请求统一设置
$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

在配置文件中添加

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

然后在网页里面导入

<script src="/static/setup.js"></script>

在页面上输入transfer,即可。

在views中定义两个函数

# @csrf_exempt  # 不校验 csrf
def index(request):
    return HttpResponse('index')


@csrf_protect  # 校验
def login(request):
    return HttpResponse('login')

在urls中添加路由

url(r'^index/',views.index),
url(r'^login/',views.login),
然后注释掉{% csrf_token %}下面的代码,把form表单注释打开,

csrf相关的装饰器

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

换成index可以,这里login是加上装饰器进行校验。

上面的两个装饰器在CBV上面有什么异同

# @method_decorator(csrf_exempt,name='post')  # csrf_exempt不支持该方法  错误的方法,不要记,了解
				@method_decorator(csrf_exempt,name='dispatch')  # csrf_exempt 指定名字name表示给谁装
				class MyIndex(views.View):
					# @method_decorator(csrf_exempt)  # 可以给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')  # csrf_exempt不支持该方法
					def post(self,request):
						return HttpResponse('OK')		

注意:csrf_exempt这个装饰器只能给dispatch装才能生效,其他的都不会生效,页面输入xxx就返回OK了。

CBV在加装饰器的时候推荐使用method_decorator,第2、4行是等价的

导入模块:from django.utils.decorators import method_decorator

注意:csrf_protect方式全都可以 跟你普通的装饰器装饰CBV一致

# @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有两个配置文件
			一个是暴露给用户可以配置的
			
			一个是内部的一个全局的(用户配置了就用用户的,用户没有配置就用自己的)
			bj对象
			obj.name = 'egon'  # 全局
			obj.name = 'jason'  # 局部
			
			先加载全局配置 给对象设置
			然后在加载局部配置  再给对象设置
			一旦有重复的项  后者覆盖前者

auth模块

先执行数据库迁移命令后,去数据库中找到auth_user表,页面输入index,创建超级用户super user

在tools,run一下输入创建超级用户的命令createsuperuser输入超级用户名和密码就行了。刷新一下数据,就能在auth_user表中看到数据信息了。

加密密码。

如何创建超级用户(root)  
python3 manage.py createsuperuser

auth模块常用的方法

1、创建用户

from django.contrib.auth.models import User
# 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')
# 创建超级用户 需要邮箱数据

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

from django.contrib import auth  
user_obj = auth.authenticate(request,username=username,password=password) #自动给你加密  去数据库中校验

注意:必须传用户名和密码两个参数缺一不可

3、保存用户状态

auth.login(request,user_obj)

只要执行这句话,之后你可以在任意位置

通过request.user获取当前登录用户对象

4、判断用户是否登录

request.user.is_authenticated()

如果没有登录就是匿名用户

5、校验原密码是否正确

request.user.check_password(old_password)

6、修改密码

request.user.set_password(new_password)
request.user.save()  # 千万不要忘了

注意:不要忘了保存

7、注销

auth.logout(request)

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_user表字段

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

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

方式2:利用继承关系

from django.contrib.auth.models import AbstractUser
    class Userinfo(AbstractUser):
    phone = models.BigIntegerField()
    register_time = models.DateField(auto_now_add=True)

注意:一定要注意 还需要去配置文件中配置

AUTH_USER_MODEL = 'app01.Userinfo'    #应用名.表名

这么写完之后 之前所有的auth模块功能全部以你写的表为准

今日考题

1.什么是cookie和session,你能描述一下它们的工作机制吗

cookie:由服务端产生内容,浏览器接收请求后保存在本地,当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是谁

session:djang内部自动帮你调用算法生成一个随机的字符串
将产生的随机字符串返回给客户端浏览器 让浏览器保存

sessionid:随机字符串

拿着sessionid所对应的随机字符串去django_sessoion表中一一比对
如果比对上了 会将随机字符串对应的数据获取出来
自动放入request.session中供程序员调用
如果没有就是一个空字典

2.django中如何操作cookie和session,请写出尽量多的操作方法,并针对session的操作方法详细内部发生的事情,django默认的session失效时间是多久

cookie:
设置:obj.set_cookie('k1','v1')告诉浏览器设置

获取:request.COOKIES.get('k1')获取浏览器携带过来的cookie值

删除:rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值

设置超时时间:obj.set_cookie('k1','v1',max_age=3)
obj.set_cookie('k1','v1',expires=3)两个参数都是设置超时时间,并且都是以秒为单位

session:
设置:request.session['k1'] = 'v1'
第一次设置的时候会报错,因为你没有执行数据库迁移命令,生成django需要用到的一些默认表
1.djang内部自动帮你调用算法生成一个随机的字符串
2.在django_session添加数据(数据也是加密处理)
随机字符串         加密之后的数据           失效时间
ashdjsad         jsadsada
3.将产生的随机字符串返回给客户端浏览器 让浏览器保存
sessionid:随机字符串

删除:request.session.delete()
request.session.flush()
客户端和服务端全部删除  只会根据浏览器的不同删对应的数据


设置失效时间:
设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
        * 如果value是个整数,session会在该秒数后失效。
        * 如果value是个datatime或timedelta,session就会在这个时间后失效。
        * 如果value是0,用户关闭浏览器session就会失效。
        * 如果value是None,session会依赖全局session失效策略。

    django session在创建数据的时候 是针对浏览器的


django默认的session失效时间是14天

3.什么是django中间件,它的作用是什么,如何自定义中间件,里面有哪些用户可以自定义的方法,这些方法有何特点?

django中间件就类似于django的门户或者保安
所有的请求和响应都必须经过中间件才能够正常通过
并且在中间件中可以对请求和响应的数据进行处理
django中间件在设计到一些全局方面的功能时 作用非常大



五个方法
			需要掌握的
				1.process_request(self,request)  (******)
					1.请求到来的时候 会按照配置文件注册的从上往下的顺序依次经过每一个中间件里面的该方法
						如果中间件中没有定义该方法 直接跳过执行下一个
					2.一旦该方法返回了一个HttpResponse对象 那么请求不再往后走 而是走到同级别的process_response依次返回
				
				2.process_response(self,request,response)
					1.响应走的时候 会按照配置文件注册的从下往上的顺序依次经过每一个中间件里面的该方法
						该方法默认必须返回response 如果你返回了自己的HttpResponse对象 那么用户收到的就是你的
			需要了解的
				3.process_view(self,request,view_name,*args,**kwargs)
					1.路由匹配成功之后执行视图函数之前
				4.process_exception(self,request,response)
					1.视图函数中出现错误的时候自动触发
				5.process_template_response(self,request,response)
					1.返回的对象中含有render方法

4.选择题(面试题)

def add(n,i):
    return n+i
def test():
    for i in range(4):
        yield i
g=test()
for n in [1,10]:  
    g=(add(n,i) for i in g)
res=list(g)
A. res=[10,11,12,13] B. res=[11,12,13,14]
C. res=[20,21,22,23] D. res=[21,22,23,24]     (C)

解析:

第一个函数add就是实现了一个简单的加法运算
第二个函数test是一个生成器函数,如果调用它会返回一个生成器
g=test(),这一行调用了生成器函数,所以此刻g就是一个生成器(它的本质还是迭代器)
然后执行for循环,这里迷惑人的就是这个for循环,为了减少它的魔性,我们把for循环拆开来看:
    当n = 1时:
    执行了:g=(add(n,i) for i in g)
    当n = 10时:
    执行了:g=(add(n,i) for i in g)
乍一看这两行代码还是有点迷糊,但是我们要知道,生成器有个最大的特性就是惰性,当你不进行迭代时它就不进行运算,想要对生成器进行迭代有以下几种方法:
第一种:for循环,for循环的本质就是调用了iter和next方法进行了迭代
第二种:调用next方法
第三种:调用send方法
第四种:数据类型强制转换,比如使用list()强制转换。
只要没有以上四种方法进行迭代,那么生成器就没有进行运算,所以在上面的for循环中无论是n=1时还是n=10时,生成器 g 都没有参与运算,

当n = 1时,g=(add(n,i) for i in g),这个表达式的结果g 就是一个表达式,没有进行运算,g的值就是一个表达式(add(n,i) for i in g),括号里面的g实际上是test(),所以g = (add(n,i) for i in test()),仅此而已
当n = 10时,g=(add(n,i) for i in g),把n=1时的g的结果带入进去就是g=(add(n,i) for i in (add(n,i) for i in test()))

当整段代码执行到print(list(g))语句之前,g的值就是一段代码,或者你可以称之为算法,没有进行任何运算,里面的n就是n,g就是g
不过此时因为代码是按照流程执行的,并且for循环已经执行完毕,所以n的值等于10

当执行print(list(g))语句时,生成器才开始输出数据,此时执行最后一个g的赋值语句:
g=(add(n,i) for i in (add(n,i) for i in test()))
这时 n 的值等于10(因为代码是按照流程执行的,for循环已经执行完了,n的最终结果就是10),其中后面的(add(n,i) for i in test())这段代码的结果依然是个生成器,迭代后应为[10,11,12,13],所以最终的结果可以理解成:(add(n,i) for i in [10,11,12,13]),所以最终结果为:20,21,22,23