57、 Django操作session、CBV中添加装饰器、django的中间件、csrf跨站请求伪造

292 阅读11分钟

Django操作session

session简介

1.cookie是保存在浏览器上面,它的数据不安全
	1.怎么优化?
  	用session,把用户的信息保存在服务端
    
2.session它是基于cookie工作的,它的原理是:
	1.先做用户信息的认证
  2.生成一个随机字符串
  3.用这个随机字符和用户信息做个映射,保存起来,django默认是保存在MySql中
    """
		key			    data
    随机字符串   用户信息
    """
  4.服务端会把随机字符返回给浏览器,浏览器把随机字符串保存起来,下次访问的时候,一块带着随机字符串传给服务端,服务端拿着这个随机字符串去数据库中查询,如果查到了,说明已经登录过了。
  
3.cookie与session区别
    1.cookie把数据保存在浏览器;session把数据保存在服务端
    2.session保存的数据更加安全
    3.cookie设置数据是有大小限制的(最大支持4096字节);session没有数据限制大小的
    
4.session由来:
		Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。 
    另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
    
    

设置session

request.session['key']='value'

1.如果你在哎Django中默认使用session,前提是必须要django_session表
    """
    	django_session表字段:
      session_key:随机字符串
      session_data:加密之后的数据
      expire_data:过期时间(session的默认过期时间14天)
    """
2.在cookie中,session保存的key默认是:sessionid
3.当session设置多个值的时候,django_Session表中也还是有一条记录(一台计算机的一个浏览器) ---->session_data值变了,session_key没有变
	"""
	  request.session['username'] = 'nana'
    request.session['username1'] = 'nana1'
    request.session['username2'] = 'nana2'
    request.session['username3'] = 'nana3'
	"""
4. 设置session发生的事情
	1.生成了随机字符串
  2.把数据保存到了django_session表中
  	2.1 这个操作实在中间件中操作的
			"""
			MIDDLEWARE = [...,'django.contrib.sessions.middleware.SessionMiddleware',...]
			ps: 看字符串的源码:用from ...import... eg: from django.contrib.sessions.middleware import SessionMiddleware   
			"""  
    2.2 请求来的时候,是把数据准备好,此时在缓存中(内存)
    2.3 响应走的时候,才真正执行了create,insert方法
  3.把随机字符串返回给浏览器
def set_session(request):
    request.session['username'] = 'nana'
    return HttpResponse('set_session')

获取session

request.session.get('key') or request.session['key']

1.获取session发生的事情
	1.获取cookie值,名为sessionid的cookie值
  2.拿着这个cookie值去数据库中查询
  3.如果查询到了,把查询到的数据封装到request.session中
def get_session(request):
    print(request.session.get('username'))  # nana
    return HttpResponse('get_session')

删除session

request.session.delete() or request.session.flush()

1.request.session.delete() # 只删除数据库
2.request.session.flush()  # 数据库和cookie都删
def del_session(request):
    # request.session.delete()
    request.session.flush()
    return HttpResponse('del session')

Django中session中常用方法

1.获取、设置、删除Session中数据
  request.session['k1']
  request.session.get('k1',None)
  request.session['k1'] = 123
  request.session.setdefault('k1',123) # 存在则不设置
  del request.session['k1']


2.所有 键、值、键值对
  request.session.keys()
  request.session.values()
  request.session.items()
  request.session.iterkeys()
  request.session.itervalues()
  request.session.iteritems()

3.会话session的key
	request.session.session_key

4.将所有Session失效日期小于当前日期的数据删除
	request.session.clear_expired()

5.检查会话session的key在数据库中是否存在
 request.session.exists("session_key")

6.删除当前会话的所有Session数据(只删数据库)
 request.session.delete()
  
7.删除当前的会话数据并删除会话的Cookie(数据库和cookie都删)。
  request.session.flush() 
    ps:这用于确保前面的会话数据不可以再次被用户的浏览器访问
    eg:django.contrib.auth.logout() 函数中就会调用它。

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

Django中的session配置

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

CBV中加装饰器

以登录认证装饰器为例

def login_auth(fun_name):
    def inner(request,*args,**kwargs):
        if request.COOKIES.get('username'):
            res = fun_name(request,*args,**kwargs)
            return res
        else:
            return redirect('/login/')

    return inner
  
def login(request):
    # 1.获取数据
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 2.从数据库中比对数据
        user_obj = models.UserInfo.objects.filter(username=username).first()
        if not user_obj:
            return HttpResponse('用户名不存在')
        if user_obj.password == password:
            print('登陆成功')
            # 使用cookie保存用户信息
            obj = redirect('/home/')
            obj.set_cookie('username',username,max_age=3000000,)
            return obj
    return render(request, 'login.html')
  
@login_auth   # home = login_auth(home)
def home(request):

    return HttpResponse('这是home页面')

提示:post请求 ,可借助postman/apizz

CBV中加装饰器-方式一

在请求方式方法上添加语法糖@method_decorator(login_auth)

from django.views import View
from django.utils.decorators import method_decorator

class cbv_login(View):
    @method_decorator(login_auth)
    def get(self,request):
        print('get')
        return HttpResponse('get')
    def post(self,request):
        print('post')
        return HttpResponse('post')

CBV中加装饰器-方式二

在类上添加语法糖@method_decorator(login_auth,name='请求方式方法')


from django.views import View
from django.utils.decorators import method_decorator

@method_decorator(login_auth,name='get')
@method_decorator(login_auth,name='post')
class cbv_login(View):
    def get(self,request):
        print('get')
        return HttpResponse('get')
    def post(self,request):
        print('post')
        return HttpResponse('post')

CBV中加装饰器-方式三

重写dispatch(在dispatch上添加语法糖@method_decorator(login_auth))

from django.views import View
from django.utils.decorators import method_decorator

class cbv_login(View):
  
    @method_decorator(login_auth)   # 类里面所有的方法都加了装饰器
    def dispatch(self, request, *args, **kwargs):
        return super(cbv_login, self).dispatch(request, *args, **kwargs)

    def get(self,request):
        print('get')
        return HttpResponse('get')
    def post(self,request):
        print('post')
        return HttpResponse('post')

Django的中间件

django的请求生命周期流程图

django请求生命周期.jpeg

1.django的请求生命周期流程图
	1.请求来了,先经过中间件
  2.响应走了,也要经过中间件
  
2.看中间件源码
	"""
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

	"""
 ps: 1.查看中间件的源码:里面是字符串,使用from ... import ...
			eg:	from django.contrib.sessions.middleware import SessionMiddleware
          from django.middleware.security import SecurityMiddleware
      2.django自带的有七个中间件
      3.django的中间件给我们暴露了一些方法
      
      """
      class SessionMiddleware(MiddlewareMixin):
           def process_request(self, request):
                  session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
                  request.session = self.SessionStore(session_key)

           def process_response(self, request, response):
              return response      
      """
      
3.提供的几个方法
	1.了解的方法:
  	1.process_view:是与视图函数相关的
    2.process_exception:跟异常相关的
    3.process_template_response:跟模版相关的
    
  2.掌握的方法
     1.process_request:请求来的时候触发
     2.process_response:响应走的时候触发
     ps:在一个中间件中,暴露的这几个方法不是每个类都必须有的,而是需要什么就写什么
    
4.应用场景:只要是与全局相关的功能都可以想到中间件
	eg:
    1.限制频率
    2.认证登陆
    3.权限校验(RBAC)

自定义中间件

1.django默认提供的有七个中间件,它还支持程序员自己定义中间件
  
2.自定中间件的步骤
	1.在项目名下或者应用名下新建一个任意名称的文件夹
  2.在这个文件夹下面新建一个py文件
  3.在这个py文件中,新建一个类,必须继承MiddlewareMixin
  4.在这个新建的类下面写两个方法:
  	1.process_reqeust
    2.process_response
  5.一定要在配置文件的中间件里面注册你的中间件路径
  
3.需求:创建自定义的中间件

针对process_request

	1.执行顺序是按照配置文件中注册的顺序,从上往下依次执行
  2.视图函数在中间件的process_reqeust函数之后执行
  3.如果在process_request里面直接返回HttpRespponse,之后的中间件yilv不再走了,包括视图函数
from django.utils.deprecation import MiddlewareMixin

class mymiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件的process_request')

class mymiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个中间件的process_request')
   
"""
MIDDLEWARE = [...,'app01.mymidddleware.mymiddle.mymiddleware1',
    'app01.mymidddleware.mymiddle.mymiddleware2']
"""
>>>打印结果:
  	我是第一个中间件的process_request
    我是第二个中间件的process_request
    nana # 视图函数返回结果
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse

class mymiddleware1(MiddlewareMixin):
    def process_request(self,request):
      	# 可以写ip访问逻辑
        print('我是第一个中间件的process_request')
        return HttpResponse('我是第一个中间件的process_request') # 拦截

class mymiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个中间件的process_request')
        
>>>打印结果:
  	我是第一个中间件的process_request

针对process_response

1.必须返回HttpResponse
2.执行顺序:是按照配置文件的注册顺序,从下往上一次执行
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse

class mymiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件的process_request')
        # return HttpResponse('我是第一个中间件的process_request')
    def process_response(self, request, response):
        print('我是第一个中间件的process_response')
        return response  # 本质返回的也是HttpResponse

class mymiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个中间件的process_request')
    def process_response(self, request, response):
        print('我是第二个中间件的process_response')
        return response

"""
MIDDLEWARE = [...,'app01.mymidddleware.mymiddle.mymiddleware1',
    'app01.mymidddleware.mymiddle.mymiddleware2']
"""
>>>打印结果:
  我是第一个中间件的process_request
  我是第二个中间件的process_request
  1
  我是第二个中间件的process_response
  我是第一个中间件的process_response

提问-结论

研究在第一个中间件的process_reqeust方法中,直接返回HttpResponse,然后,观察所有中间件的process_response的执行顺序?

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse

class mymiddleware1(MiddlewareMixin):
    def process_request(self,request):
        print('我是第一个中间件的process_request')
        return HttpResponse('我是第一个中间件的process_request')
    def process_response(self, request, response):
        print('我是第一个中间件的process_response')
        return response

class mymiddleware2(MiddlewareMixin):
    def process_request(self, request):
        print('我是第二个中间件的process_request')
    def process_response(self, request, response):
        print('我是第二个中间件的process_response')
        return response
      
>>>打印结果:
  我是第一个中间件的process_request
  我是第一个中间件的process_response
  # 页面返回:我是第一个中间件的process_request 
结论:在第一个中间件中得process_reqeust中直接拦截,只走第一个中间件的process_reqeust和第一个中间件的process_response,直接原路返回。
		但是,在flask框架中,如果在第一个拦截了,后面的类似于process_response都会走一遍(flask框架中实际上没有没有真正的中间件,它是需要我们看源码自己做成类似于django的这种中间件)

csrf跨站请求伪造

钓鱼网站

1.最典型的例子是钓鱼网站,所谓钓鱼网站,就是它的页面与真实网站界面基本一致,欺骗消费者或者窃取访问者提交的账号和密码信息

2.举例:
	现在有一个正规的中国银行的网站,诈骗A写一个页面与中国银行的页面完全一模一样的网站,有个客户B去中国银行转钱,不小心来到了这个钓鱼网站,开始在这个钓鱼网站转账,转账完毕之后,钱确实少了,这个钱也到了中国银行,但是,它想转账的这个对方账户不是它自己想要的哪个账户.
  
3.针对上述情况,它是怎么做到的?
	其内部本质是:在钓鱼网站上的form表单中,它是写一个没有name属性的input框,它在这个input框下面,在隐藏一个input框,这个隐藏的input框,我给他提前写好name属性和value值,但是用户不知道
  
4.如何规避上述问题?
	在正规网站中给form表单一个唯一标识,然后,表单每次提交的时候,后端都做一个验证没如果正确就可以提交,如果不正确,就直接403(forbidden)
  
ps:面试的时候关于面试相关的:sql注入,xss攻击,csrf跨站请求,密码加盐

模拟钓鱼网站

正规网站

# views.py
def home(request):
    if request.method =='POST':
        username = request.POST.get('username')
        target_name = request.POST.get('target_name')
        money = request.POST.get('money')
        print(f'用户{username}{target_name}转账了{money}')
    return render(request,'home.html')
# home.html
<body>
<h1>中国银行官方网站</h1>
<form action="" method="post">
    username: <input type="text" name ='username'>
    target_name: <input type="text" name = 'target_name'>
    money: <input type="text" name = 'money'>
    <input type="submit">
</form>

</body>

钓鱼网站

# views.py
def home(request):
    return render(request,'home.html')
#  home.html 
<body>
<h1>伪造:中国银行官方网站</h1>
<form action="http://127.0.0.1:8000/home/" method="post">
    username: <input type="text" name ='username'>
    target_name: <input type="text" >
    <input type="text" name="target_name" style="display: none" value="xiao">
    money: <input type="text" name = 'money'>
    <input type="submit">
</form>
</body>

解决问题

在正规网站中,给form表单一个唯一标识,然后,表单每次提交的时候,后端做一个验证,如果正确就可以提交,如果不正确,就直接403(forbidden)

<body>
<h1>中国银行官方网站</h1>
<form action="" method="post">
    {% csrf_token %}
    username: <input type="text" name ='username'>
    target_name: <input type="text" name = 'target_name'>
    money: <input type="text" name = 'money'>
    <input type="submit">
</form>
</body>

ps:1.在form表单中加入{% csrf_token %},在浏览器端就多了一个标签:<input type="hidden" name="csrfmiddlewaretoken" value="ytTLI7TrhKTHW8rpLMOfqQhoKJBCyPQebbWAQpD0NzYNb1RsSE9bzSTtZ3c8REEr">;其value值是随机的(标识),form表单提交的时候,中间件的csrf进行验证,如果正确就可以提交,如果不正确,就直接403(forbidden)

ajax如何规避csrf的验证

方式一

data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]'}

<body>
<h1>中国银行官方网站</h1>
<button class="btn">确认</button>
<script>
    $('.btn').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'nana','target_name':'cx','money':10000,'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()}, //方式一
            success:function (){

            }
        })
    })
</script>
</body>

方式二

data:{'csrfmiddlewaretoken':'{{ csrf_token }}'}

<body>

<h1>中国银行官方网站</h1>
<button class="btn">确认</button>
<script>
    $('.btn').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'nana','target_name':'cx','money':10000,'csrfmiddlewaretoken':'{{ csrf_token }}'},
            success:function (){

            }
        })
    })
</script>

方式三

使用django官方提供的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');


// 每一次都这么写太麻烦了,可以使用$.ajaxSetup()方法为ajax请求统一设置。

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);
    }
  }
});

将上面的文件配置到你的Django项目的静态文件中,在html页面上通过导入该文件即可自动帮我们解决ajax提交post数据时校验csrf_token的问题,(导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的);更多细节详见:docs.djangoproject.com/en/1.11/ref…

<body>

<h1>中国银行官方网站</h1>


<button class="btn">确认</button>
<script src="/static/js/myjs.js">  //导入django官方提供的js文件
</script>
<script>
    $('.btn').click(function (){
        $.ajax({
            url:'',
            type:'post',
            {#data:{'username':'nana','target_name':'cx','money':10000,'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},#}
            {#data:{'username':'nana','target_name':'cx','money':10000,'csrfmiddlewaretoken':'{{ csrf_token }}'},#}
            data:{'username':'nana','target_name':'cx','money':10000},
            success:function (){

            }
        })
    })
</script>
</body>