目录javascript
CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击同样,存在巨大的危害性,你能够这样来理解:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来讲这个请求是彻底合法的,可是却完成了攻击者所指望的一个操做,好比以你的名义发送邮件、发消息,盗取你的帐号,添加系统管理员,甚至于购买商品、虚拟货币转帐等。html
人设:Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。
CSRF攻击攻击原理及过程以下:前端
PS:还有一种状况是:多窗口浏览器,它便捷的同时也带来了一些问题,由于多窗口浏览器新开的窗口是具备当前全部会话的。即我用IE登录了个人Blog,而后我想看新闻了,又运行一个IE进程,这个时候两个IE窗口的会话是彼此独立的,从看新闻的IE发送请求到Blog不会有我登陆的cookie;可是多窗口浏览器永远都只有一个进程,各窗口的会话是通用的,即看新闻的窗口发请求到Blog是会带上我在blog登陆的cookie,也有可能产生CSRF攻击。java
下面是一个实例:python
1. 受害者 Bob 在银行有一笔存款,经过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可使 Bob 把 1000000 的存款转到 bob2 的帐号下。一般状况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,而且该 session 的用户 Bob 已经成功登录。 2. 黑客 Mallory 本身在该银行也有帐户,他知道上文中的 URL 能够把钱进行转账操做。Mallory 能够本身发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。可是这个请求来自 Mallory 而非 Bob,他不能经过安全认证,所以该请求不会起做用。 3. 这时,Mallory 想到使用 CSRF 的攻击方式,他先本身作一个网站,在网站中放入以下代码: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,而且经过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一块儿发向银行服务器。大多数状况下,该请求会失败,由于他要求 Bob 的认证信息。可是,若是 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 还没有过时,浏览器的 cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会获得响应,钱将从 Bob 的帐号转移到 Mallory 的帐号,而 Bob 当时绝不知情。等之后 Bob 发现帐户钱少了,即便他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则能够拿到钱后逍遥法外。
目前防护 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。jquery
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在一般状况下,访问一个安全受限页面的请求来自于同一个网站,好比须要访问 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登录 bank.example,而后经过点击页面上的按钮来触发转帐事件。这时,该转账请求的 Referer 值就会是转帐按钮所在的页面的 URL,一般是以 bank.example 域名开头的地址。而若是黑客要对银行网站实施 CSRF 攻击,他只能在他本身的网站构造请求,当用户经过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客本身的网站。所以,要防护 CSRF 攻击,银行网站只须要对于每个转帐请求验证其 Referer 值,若是是以 bank.example 开头的域名,则说明该请求是来自银行网站本身的请求,是合法的。若是 Referer 是其余网站的话,则有多是黑客的 CSRF 攻击,拒绝该请求。nginx
这种方法的显而易见的好处就是简单易行,网站的普通开发人员不须要操心 CSRF 的漏洞,只须要在最后给全部安全敏感的请求统一增长一个拦截器来检查 Referer 的值就能够。特别是对于当前现有的系统,不须要改变当前系统的任何已有代码和逻辑,没有风险,很是便捷。程序员
然而,这种方法并不是万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,可是每一个浏览器对于 Referer 的具体实现可能有差异,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来说,这样并不安全。事实上,对于某些浏览器,好比 IE6 或 FF2,目前已经有一些方法能够篡改 Referer 值。若是 bank.example 网站支持 IE6 浏览器,黑客彻底能够把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就能够经过验证,从而进行 CSRF 攻击。ajax
即使是使用最新的浏览器,黑客没法篡改 Referer 值,这种方法仍然有问题。由于 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们本身的隐私权,特别是有些组织担忧 Referer 值会把组织内网中的某些信息泄露到外网中。所以,用户本身能够设置浏览器使其在发送请求时再也不提供 Referer。当他们正常访问银行网站时,网站会由于请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。redis
CSRF 攻击之因此可以成功,是由于黑客能够彻底伪造用户的请求,该请求中全部的用户验证信息都是存在于 cookie 中,所以黑客能够在不知道这些验证信息的状况下直接利用用户本身的 cookie 来经过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,而且该信息不存在于 cookie 之中。能够在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端创建一个拦截器来验证这个 token,若是请求中没有 token 或者 token 内容不正确,则认为多是 CSRF 攻击而拒绝该请求。
这种方法要比检查 Referer 要安全一些,token 能够在用户登录后产生并放于 session 之中,而后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址以后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来讲,要在 form 的最后加上 ,这样就把 token 以参数的形式加入请求了。可是,在一个网站中,能够接受请求的地方很是多,要对于每个请求都加上 token 是很麻烦的,而且很容易漏掉,一般使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中全部的 a 和 form 标签后加入 token。这样能够解决大部分的请求,可是对于在页面加载以后动态生成的 html 代码,这种方法就没有做用,还须要程序员在编码时手动添加 token。
该方法还有一个缺点是难以保证 token 自己的安全。特别是在一些论坛之类支持用户本身发表内容的网站,黑客能够在上面发布本身我的网站的地址。因为系统也会在这个地址后面加上 token,黑客能够在本身的网站上获得这个 token,并立刻就能够发动 CSRF 攻击。为了不这一点,系统能够在添加 token 的时候增长一个判断,若是这个连接是链到本身本站的,就在后面添加 token,若是是通向外网则不加。不过,即便这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也一样能够经过 Referer 来获得这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的缘由。
这种方法也是使用 token 并进行验证,和上一种方法不一样的是,这里并非把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。经过 XMLHttpRequest 这个类,能够一次性给全部该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,经过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担忧 token 会透过 Referer 泄露到其余网站中去。
然而这种方法的局限性很是大。XMLHttpRequest 请求一般用于 Ajax 方法中对于页面局部的异步刷新,并不是全部的请求都适合用这个类来发起,并且经过该类请求获得的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操做,给用户带来不便。另外,对于没有进行 CSRF 防御的遗留系统来讲,要采用这种方法来进行防御,要把全部请求都改成 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。
django中,是经过中间件来对CSRF进行验证的,有关中间件的信息,将在后面的小节中说明。PS:还记得以前在settings.py中注释掉的 django.middleware.csrf.CsrfViewMiddleware 吗?,它就是用于检测CSRF的中间件。若是咱们render返回页面时,那么用户使用get请求访问网站时,站点会返回一段随机的csrf_token,同时也会在cookie中写一个名为csrftoken的key,这两个key对应的值是不一样的。
根据这两种token,咱们有两种方式,传递、验证CSRF token(首先打开csrf中间件,若是以前注释掉的话)即:一、form表单提交 二、ajax提交
在表单中添加{% csrf_token %} 来渲染服务端返回的csrf_token,它会以hidden的形式在form中产生一个input标签,在咱们利用form提交时会一并携带发送给服务端进行验证,当咱们使用ajax代替form的submit进行提交时,能够在data字段,来获取form表单中渲染的csrf_token,进行提交,固然,这里的key的名称必须为csrfmiddlewaretoken,不然没法识别,从生成的input的name属性就能看到。
<form action="/login" method="post" > {% csrf_token %} #渲染csrf_token <div> <label for="user">用户名:</label> <input type="text" id="user" name="user" placeholder='Username'> </div> <div> <label for="passwd">密码:</label> <input type="password" id="passwd" name="passwd" placeholder='Password'> </div> <input type="submit" value="登录"> </form>
在前端咱们看到的是:
<form action="/file" method="post" enctype="multipart/form-data"> <input type="hidden" name="csrfmiddlewaretoken" value="8HElv24C9ajJZUE8gX3Cs9e0iIaXgX9iMvIfXZsKthjazqwnAvtrEvS84QC2pEik"> # 渲染的input标签 <div> <label for="user">用户名:</label> <input type="text" id="user" name="user" placeholder='Username'> </div> <div> <label for="passwd">密码:</label> <input type="password" id="passwd" name="passwd" placeholder='Password'> </div> <input type="submit" value="登录"> </form>
经过AJAX来获取Form的csrftoken进行提交
$(function(){ $('#btn').click(function () { $.ajax({ url:'/login', type:'POST', data:{'csrfmiddlewaretoken':$("input[name='csrfmiddlewaretoken']").val(),......}, // name属性选择器获取csrftoken success:function () { alert(123) } }) }) })
为何是'csrfmiddlewaretoken'这个名字?
# 一、倒入 csrf中间件查看源码,获取的究竟是什么信息 from django.middleware.csrf import CsrfViewMiddleware # 二、源码中关于from的csrf定义 295行 request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') # 这里获取了csrfmiddlewaretoken,因此咱们form必需要传递这个key才行。
当页面中没有form表单(或者说不想使用form手动渲染),那么还可使用cookie中的csrftoken进行csrf验证,因为cookie在请求时是放在请求头中的,因此这里使用ajax的headers来定制请求头,注意请求头的key必须为(X-CSRFtoken),利用jquery cookie插件,使用$.cookie('csrftoken')来获取对应的值便可。
$(function(){ $('#btn').click(function () { $.ajax({ url:'/index', type:'post', headers:{'X-CSRFtoken':$.cookie('csrftoken')}, // 构建请求头 data:{'user':$('#user').val(),'passwd':$('#passwd').val()}, success:function () { alert(123) } }) }) })
为何是'X-CSRFtoken'这个名字?
# 一、倒入 csrf中间件查看源码,获取的究竟是什么信息 from django.middleware.csrf import CsrfViewMiddleware # 二、源码中关于header中的csrf说明,295行(django 1.11) request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') except IOError: pass if request_csrf_token == "": request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') # 若是用户提交的数据中没有request_csrf_token,那么尝试从meta中获取数据(包含用户提交的全部数据也包括请求头,从中获取settings.CSRF_HEADER_NAME的值用做request_csrf_token) # 三、倒入settings查找csrftoken的真正名称 from django.conf import settings print(settings.CSRF_HEADER_NAME) # 结果为:HTTP_X_CSRFTOKEN # 四、当咱们利用ajax定制 request headers时,django会为咱们在咱们定制的key前面添加HTTP_来进行标识, # 因此咱们实际上传递的key应该为X_CSRFTOKEN, # 又由于提交数据时不能提交包含下划线的key,会被认为是非法数据,因此,这里的名称为X-CSRFTOKEN # 官方建议为:X-CSRFtoken
PS:这里的settings模块和settings.py文件不是一回事儿,settings模块中包含了django不少的默认配置信息,若是咱们要对某个配置信息进行修改,能够在settings.py中定义,若是没有定义,那么django就会按照settings中的默认配置进行处理。
扩展信息:
在商城 APP 开发时,在与客户端联调 API 接口的过程当中,咱们发现,在 PHP 的 $_SERVER 超全局变量中某些自定义的 HEADER 字段竟然获取不到。经过抓包工具查看数据包,该自定义头的确是存在的。
后来经过调试咱们发现,根本缘由是客户端错误地将字段名中的中划线写成了下划线。例如,应该是 X-ACCESS-TOKEN,却被写成了 X_ACCESS_TOKEN。
问题自己很好解决。然而咱们想要知道,服务器为什么要对字段名中使用了下划线的头视而不见呢?而且,无论是 Apache 仍是 Nginx,对于这样的状况,都不约而同地采起了同样的策略。
在 RFC 2616 4.2 节中,有以下一段话:
Request (section 5) and Response (section 6) messages use the generic message format of RFC 822 [9] for transferring entities (the payload of the message).
这段话的意思,就是说 HTTP/1.1 的请求和响应消息使用 RFC 822 中的通用消息格式来传输实体(消息载荷)。
在 RFC 822 3.1.2 节中,对于消息格式的说明,有这样一句话:
The field-name must be composed of printable ASCII characters (i.e., characters that have values between 33. and 126., decimal, except colon).
也就是说,HEADER 字段名能够可由可打印的 ASCII 字符组成(也就是十进制值在 33 和 126 之间的字符,不含冒号)。
不含冒号很容易理解,由于 Field-Name 和 Value 之间须要用冒号分割。然而,咱们经过查询 ASCII 码表可知,下划线的十进制 ASCII 值为 95,也在此范围以内!
其实,在 HEADER 字段名中使用下划线实际上是合法的、符合 HTTP 标准的。服务器之因此要默认禁止使用是由于 CGI 历史遗留问题。下划线和中划线都为会被映射为 CGI 系统变量中名中的下划线,这样容易引发混淆。
在 nginx 服务器中,经过显式地设置 underscores_in_headers on 能够开启在字段名中使用下划线。默认该选项是关闭的,因此在默认状况下,全部包含下划线的字段名都会被丢弃。
在咱们的开发机中的确也开启了这个选项,为啥仍是不能拿到字段名中包含下划线的 HEADER 呢?这是由于咱们访问这台开发机的时候,前面还有一层代理服务器,而这台代理服务器没有开启相关选项,致使这种 HEADER 被代理服务器直接丢弃。所以也强烈建议不要在 HEADER 的 Field-Name 中使用下划线。
当咱们对以前的ajax提交进行改造时,因为以前没有csrftoken的验证,那么若是咱们开启验证,那么就须要对全部的ajax提交进行修改,那么这里可使用ajax的setup进行全局的配置(能够查看上一篇ajax部份内容)。
$(function(){ // 利用ajaxSetup进行全局配置 $.ajaxSetup({ beforeSend:function (xhr,settings) { // 在发送ajax请求以前,要执行的函数 xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')) // 定制请求头,添加csrftoken } }) // 其余ajax请求,就无需再定制header发送csrf了。 $('#btn').click(function () { $.ajax({ url:'/index', type:'post', data:{'user':$('#user').val(),'passwd':$('#passwd').val()}, success:function () { alert(123) } }) }) })
PS:好比get,delete等请求是不须要携带csrftoken的,那么咱们可使用在进行全局配置时,对HTTP请求方式进行判断。
# 判断HTTP请求方式。针对POST请求提交CSRFtoken <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> {% csrf_token %} <input type="button" onclick="Do();" value="Do it"/> <script src="/static/plugin/jquery/jquery-1.8.0.js"></script> <script src="/static/plugin/jquery/jquery.cookie.js"></script> <script type="text/javascript"> var csrftoken = $.cookie('csrftoken'); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); # 如过是GET|HEAD|OPTION|TRACE 这四种请求,那么返回False } $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { # 针对非GET|HEAD|OPTION|TRACE的请求添加CSRFtoken xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); function Do(){ $.ajax({ url:"/app01/test/", data:{id:1}, type:'POST', success:function(data){ console.log(data); } }); } </script> </body> </html>
在咱们的网站中,其实并非全部的站点都须要进行csrftoken验证,那么如何对指定的站点函数进行csrf验证或者排除呢?
@csrf_protect
,为当前函数强制设置防跨站请求伪造功能,即settings中没有设置全局中间件。@csrf_exempt
,取消当前函数防跨站请求伪造功能,即settings中设置了全局中间件。使用csrf装饰器须要先进行引用:from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.shortcuts import render,HttpResponse,redirect from django.views.decorators.csrf import csrf_exempt,csrf_protect # 导入csrf装饰器 @csrf_exempt # 设置该函数取消csrf认证 def index(request): error_msg = '' if request.method == 'POST': user = request.POST.get('user') passwd = request.POST.get('passwd') if user == 'daxin' and passwd == '123456' or user == 'dachenzi' and passwd == '123456': request.session['username'] = 'daxin' request.session['is_login'] = True return redirect('/page') else: error_msg = '用户名或密码错误' return render(request,'index.html',{'error_msg':error_msg,'test':{'name':'daxin','age':20}})
因为HTTP协议是无状态的协议,因此服务端须要记录用户的状态时,就须要用某种机制来识具体的用户,这个机制就是Session.典型的场景好比购物车,当你点击下单按钮时,因为HTTP协议无状态,因此并不知道是哪一个用户操做的,因此服务端要为特定的用户建立了特定的Session,用用于标识这个用户,而且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个惟一标识。在服务端保存Session的方法不少,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,通常会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务好比Memcached之类的来放 Session。服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次建立Session的时候,服务端会在HTTP协议中告诉客户端,须要在 Cookie 里面记录一个Session ID,之后每次请求把这个会话ID发送到服务器,我就知道你是谁了。
用一句话总结的话:
Cookie 能够翻译为“小甜品,小饼干” ,Cookie 在网络系统中几乎无处不在,当咱们浏览之前访问过的网站时,网页中可能会出现 :你好 XXX,这会让咱们感受很亲切,就好像吃了一个小甜品同样。这实际上是经过访问主机中的一个文件来实现的,这个文件就是 Cookie。在 Internet 中,Cookie 其实是指小量信息,是由 Web 服务器建立的,将信息存储在用户计算机上的文件,不一样的网站有不一样的cookie,因此又成为cookies。
当 Web 服务器建立了Cookies 后,只要在其有效期内,当用户访问同一个 Web 服务器时,浏览器首先要检查本地的Cookies,并将其原样发送给 Web 服务器。而当用户结束浏览器会话时,系统将终止全部的 Cookie。
目前有些 Cookie 是临时的,有些则是持续的。临时的 Cookie 只在浏览器上保存一段规定的时间,一旦超过规定的时间,该 Cookie 就会被系统清除,而持续性的能够永久进行存储。
PS:一个浏览器能建立的 Cookie 数量最多为 300 个,而且每一个不能超过 4KB,每一个 Web 站点能设置的 Cookie 总数不能超过 20 个。
在django中使用Cookie
如今有这样一个场景,index页面须要用户首先访问login页面,登录成功后才能访问index页面,针对这个需求,若是只使用咱们前面介绍的知识是没法进行完成的,而利用cookie则很是简单。
# ------------------- views.py ------------------- def index(request): if request.method == 'GET': username = request.COOKIES.get('user') # 获取用户请求中携带的cookies return render(request,'index.html',{'username':username}) def login(request): if request.method == 'GET': return render(request,'login.html') if request.method == 'POST': u = request.POST.get('username') p = request.POST.get('password') if u == 'daxin' and p == '123123': req = redirect('/index') req.set_cookie('user',u) # 设置cookie return req else: return render(request,'login.html')
针对cookies,django 提供了设置以及获取的方法,而且还有可选的参数。
# 用变量接受返回用户的信息 response = redirect('/index') response = render(request,'index.html') response = Httpresponse('index') # 设置cookie response.set_cookie('key','value') # 获取cookie request.COOKIE.get('key')
set_cookie方法还提供了其余的参数用于配置cookie:
req.set_cookie('username','abc',max_age=10,path='/',......)
咱们在浏览器上能够看到咱们指定的cookie中的key值,这样不是特别安全,那么咱们能够对cookie进行加密。django提供cookie的加密方法。
req.set_signed_cookie('name','abc.123',salt='abc.123') # 设置签名的cookie request.get_signed_cookie('name',salt='abc.123',default='null') # 获取签名的cookie
PS:salt表示要加的签名,获取时,必需要传递对应的签名,才能够正确获取。
一般咱们在数据列表页面下面会看到供用户选择的页面数据显示条目数,一旦肯定之后,那么之后选择下一页上一页,将会针对用户选择的条目数生成新的页码和数据,那么这个功能是怎么实现的呢?如何才能让浏览器记住用户选择的数据呢?这里经过cookie就能实现。
# -------------------- html -------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .page { text-decoration: none; margin-right: 3px; } .active { background-color: saddlebrown; color: white; } </style> </head> <body> <!-- 模拟数据显示 --> <ul> {% for item in data %} <li>{{ item }}</li> {% endfor %} </ul> <div> <select name="count" id='count' onchange="pageChange(this)"> <option value="10">10</option> <option value="30">30</option> <option value="50">50</option> <option value="100">50</option> </select> </div> {{ temp_list | safe }} <script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $(function () { var val = $.cookie('page_size'); if (val) { $('#count').val(val); // 页面加载完毕,赋值页面数量 } }); function pageChange(ths) { var val = $(ths).val(); // 获取用户设置的页面条目数 $.cookie('page_size',val); // 写到cookie中(jquery.cookie),额外的属性能够直接根{'path':'/',......} location.reload() } </script> </body> </html> -------------------- views -------------------- from utils import pagination def page(request): if request.method == 'GET': page_size = request.COOKIES.get('page_size') # 获取page_size current_page = int(request.GET.get('page', 1)) counts = len(all_data) if page_size: # 若是用户设置了page_size,修改生成的html page_obj = pagination.Page(current_page,counts,per_page_count=int(page_size)) else: page_obj = pagination.Page(current_page, counts) data = all_data[page_obj.start:page_obj.end] temp_list =page_obj.page_str('/page/') return render(request,'page.html',{'data':data,'current_page':current_page,'temp_list':temp_list})
不少时候,好比咱们要访问index页面,须要先进行登录,不登录那么拒绝访问index页面,这种场景下使用cookie能够很方便的完成。
def auth(func): def index (request,*args,**kwargs): if not request.COOKIES.get('username'): # 判断cookie中是否存在咱们指定的key return redirect('/login') # 不存在则跳转回login页面 return func(request,*args,**kwargs) return index
PS:这里利用装饰器的方式对传入的function进行cookie验证,若是验证失败,那么跳转回login页面,在须要cookie验证的地方装饰便可
@auth def index(request): ... ...
扩展
req = redirect('/index') req.set_signed_cookie('name','abc.123',salt='abc.123',max_age=10,path='/') req['user'] = 'abc' # 添加响应头数据 return req
之因此要把 cookie和session放在一块儿说,是由于它俩是有关系的,因为cookie 是存在用户端,并且它自己存储的尺寸大小也有限,最关键是用户能够是可见的,并能够随意的修改,很不安全。那如何又要安全,又能够方便的全局读取信息呢?因而,这个时候,一种新的存储会话机制:session 诞生了。session的机制是基于session_id的,它用来区分哪几回请求是一我的发出的。为何要这样呢?由于HTTP是无状态无关联的,一个页面可能会被成百上千人访问,并且每一个人的用户名是不同的,那么服务器如何区分相同用户访问的呢?因此就有了找个惟一的session_id来绑定一个用户。一个用户在一次会话上就是一个session_id,这样成千上万的人访问,服务器也能区分究竟是谁在访问了。而session_id,就是经过cookie答复给客户端的,因此session依赖cookie,即解决了安全问题,又能在服务端存储用户相关个性化数据。
首先根据session的工做原理,咱们能够大体的分析出服务端对session的处理步骤。
服务端设置session:
服务端获取session:
request封装的session,可使用相似字典的方式进行操做。
# 用户session的随机字符串 request.session.session_key # 将全部session失效日期小于当前日期的数据删除 request.session.clear_expired() # 检查 用户的随机字符串 在数据库中是否存在 request.session.exists('session_key') # 删除当前用户的全部Session数据 request.session.delete('session_key') # 清空全部session request.session.clear() # django中的session默认有效期为两周,单独设置某个session的超时时间须要进行以下修改 request.session.set_expiry(value) # 若是value是个整数(或者是个乘法表达式 60 * 60),session会在些秒数后失效。 # 若是value是个datatime或timedelta,session就会在这个时间后失效。 # 若是value是0,用户关闭浏览器session就会失效。 # 若是value是None,session会依赖全局session失效策略。
PS:若是咱们在session中写入了用户名称,须要在页面上展现,并不须要在后端获取(request.session.get('username')),在后在前端渲染({{ username }}), 由于咱们渲染页面的同时,传递了request对象,request对象又包涵了咱们应答的全部信息,因此能够在页面上直接利用{{ request.session.username }} 进行渲染。
上面使用cookie 对主页进行了登录验证,这里使用session配合装饰器,完成页面登录验证。
def auth(func): def inner(request,*args,**kwargs): if request.session.get('is_login',None): return func(request,*args,**kwargs) else: return redirect('/login/') return inner @auth def index(request): if request.method == 'GET': return render(request,'index.html',{'username':request.session['username']}) def login(request): if request.method == 'GET': return render(request,'login.html') if request.method == 'POST': u = request.POST.get('username') p = request.POST.get('password') if u == 'daxin' and p == '123123': request.session['username'] = u request.session['is_login'] = True return redirect('/index/') else: return render(request,'login.html')
PS:django默认会把session_id和数据的对应关系放在数据库中,通常存放在django_session表中,若是是新建的项目,须要先执行以下命令生成表才可使用。
python3 manage.py makemigrations python3 manage.py migrate
默认状况下session是有默认配置的,好比默认超时时间为两周,若是咱们要修改全局配置,那么须要在settings.py中进行配置
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,默认修改以后才保存,建议修改成True
PS:上面的参数都是默认配置,若是须要修改单个配置,只须要添加对应的key和value就能够了。
不少时候基于业务的需求,咱们须要把session放在别的地方,好比缓存或者文件中,那么就须要使用以下配置了
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 默认配置,表示存放在数据库中 SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 使用cache进行存储 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也能够是memcache),此处别名依赖缓存的设置
上面cache_alias 实际上是咱们在配置文件中指定的chache的别名,由于咱们若是要写到memcahed中,那么就须要写链接地址以及端口,那么就须要在cache中进行定义
CACHES = { 'default': { # 这里就表示cache的别名 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
PS:django默认支持memcached,不支持redis,须要安装其余插件。下面是缓存到文件中去
'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 缓存文件路径,若是为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T