Web框架之Django【进阶篇】- 分页 Xss攻击 Cookie Session CSRF 装饰器 缓存 信号

分页

一、Django内置分页

views.py

from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

L = []
for i in range(999):
    L.append(i)

def index(request):
    current_page = request.GET.get('p')

    paginator = Paginator(L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'index.html', {'posts': posts})

html:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<ul>
    {% for item in posts %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<div class="pagination">
      <span class="step-links">
        {% if posts.has_previous %}
            <a href="?p={{ posts.previous_page_number }}">Previous</a>
        {% endif %}
          <span class="current">
            Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
          </span>
          {% if posts.has_next %}
              <a href="?p={{ posts.next_page_number }}">Next</a>
          {% endif %}
      </span>

</div>
</body>
</html>

扩展内置分页:views.py:

from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

class CustomPaginator(Paginator):
    def __init__(self, current_page, max_pager_num, *args, **kwargs):
        """
        :param current_page: 当前页
        :param max_pager_num:最多显示的页码个数
        :param args:
        :param kwargs:
        :return:
        """
        self.current_page = int(current_page)
        self.max_pager_num = max_pager_num
        super(CustomPaginator, self).__init__(*args, **kwargs)

    def page_num_range(self):
        # 当前页面
        # self.current_page
        # 总页数
        # self.num_pages
        # 最多显示的页码个数
        # self.max_pager_num
        print(1)
        if self.num_pages < self.max_pager_num:
            return range(1, self.num_pages + 1)
        print(2)
        part = int(self.max_pager_num / 2)
        if self.current_page - part < 1:
            return range(1, self.max_pager_num + 1)
        print(3)
        if self.current_page + part > self.num_pages:
            return range(self.num_pages + 1 - self.max_pager_num, self.num_pages + 1)
        print(4)
        return range(self.current_page - part, self.current_page + part + 1)

L = []
for i in range(999):
    L.append(i)

def index(request):
    current_page = request.GET.get('p')
    paginator = CustomPaginator(current_page, 11, L, 10)
    # per_page: 每页显示条目数量
    # count:    数据总个数
    # num_pages:总页数
    # page_range:总页数的索引范围,如: (1,10),(1,200)
    # page:     page对象
    try:
        posts = paginator.page(current_page)
        # has_next              是否有下一页
        # next_page_number      下一页页码
        # has_previous          是否有上一页
        # previous_page_number  上一页页码
        # object_list           分页之后的数据列表
        # number                当前页
        # paginator             paginator对象
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request, 'index.html', {'posts': posts})

扩展内置分页:Html:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

<ul>
    {% for item in posts %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
    <a href="?p={{ posts.previous_page_number }}">Previous</a>
{% endif %}

    {% for i in posts.paginator.page_num_range %}
        <a href="?p={{ i }}">{{ i }}</a>
    {% endfor %}

    {% if posts.has_next %}
        <a href="?p={{ posts.next_page_number }}">Next</a>
    {% endif %}
</span>

<span class="current">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>

</div>
</body>
</html>

二、自定义分页

分页功能在每个网站都是必要的,对于分页来说,其实就是根据用户的输入计算出应该在数据库表中的起始位置。

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页...)

3、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

4、在数据表中根据起始位置取值,页面上输出数据

扫描二维码关注公众号,回复: 7940318 查看本文章

需求又来了,需要在页面上显示分页的页面。如:[上一页][1][2][3][4][5][下一页]

1、设定每页显示数据条数

2、用户输入页码(第一页、第二页...)

3、设定显示多少页号

4、获取当前数据总条数

5、根据设定显示多少页号和数据总条数计算出,总页数

6、根据设定的每页显示条数和当前页码,计算出需要取数据表的起始位置

7、在数据表中根据起始位置取值,页面上输出数据

8、输出分页html,如:[上一页][1][2][3][4][5][下一页]

总结,分页时需要做三件事:

  • 创建处理分页数据的类
  • 根据分页数据获取数据
  • 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]

实例:

#!/usr/bin/env python
# _*_coding:utf-8_*_
from django.utils.safestring import mark_safe
 
class PageInfo(object):
    def __init__(self,current,totalItem,peritems=5):
        self.__current=current
        self.__peritems=peritems
        self.__totalItem=totalItem
    def From(self):  # 切片起始
        return (self.__current-1)*self.__peritems
    def To(self):  # 切片结束
        return self.__current*self.__peritems
    def TotalPage(self):  #总页数
        result=divmod(self.__totalItem,self.__peritems)
        if result[1]==0:
            return result[0]
        else:
            return result[0]+1
 
def Custompager(baseurl,currentPage,totalpage):  #基础页,当前页,总页数
    perPager=11
    #总页数<11
    #0 -- totalpage
    #总页数>11
        #当前页大于5 currentPage-5 -- currentPage+5
            #currentPage+5是否超过总页数,超过总页数,end就是总页数
        #当前页小于5 0 -- 11
    begin=0
    end=0
    if totalpage <= 11:
        begin=0
        end=totalpage
    else:
        if currentPage>5:
            begin=currentPage-5
            end=currentPage+5
            if end > totalpage:
                end=totalpage
        else:
            begin=0
            end=11
    pager_list=[]
    if currentPage<=1:
        first="<a href=''>首页</a>"
    else:
        first="<a href='%s%d'>首页</a>" % (baseurl,1)
    pager_list.append(first)
 
    if currentPage<=1:
        prev="<a href=''>上一页</a>"
    else:
        prev="<a href='%s%d'>上一页</a>" % (baseurl,currentPage-1)
    pager_list.append(prev)
 
    for i in range(begin+1,end+1):
        if i == currentPage:
            temp="<a href='%s%d' class='selected'>%d</a>" % (baseurl,i,i)
        else:
            temp="<a href='%s%d'>%d</a>" % (baseurl,i,i)
        pager_list.append(temp)
    if currentPage>=totalpage:
        next="<a href='#'>下一页</a>"
    else:
        next="<a href='%s%d'>下一页</a>" % (baseurl,currentPage+1)
    pager_list.append(next)
    if currentPage>=totalpage:
        last="<a href=''>末页</a>"
    else:
        last="<a href='%s%d'>末页</a>" % (baseurl,totalpage)
    pager_list.append(last)
    result=''.join(pager_list)
    return mark_safe(result)   #把字符串转成html语言

Xss攻击(Html代码的安全性):

模板中:
{{ page_str|safe }}
或:
Views中:
mark_safe(page_str)

Cookie

是客户端浏览器上的一个文件,即保存在用户浏览器上的一个键值对 {"user": 'dachengzi'},客户端、服务器端都可读可写。

1、获取Cookie:

DOM获取:document.cookie,基于dom做比较麻烦,还需要设置一大堆东西,如超时时间等等

jQuery获取(基于cookie插件):$.cookie

request.COOKIES['key'] # 或:request.COOKIES.get('key')
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
    参数:
        default: 默认值
           salt: 加密盐  (hashlib密文)
        max_age: 后台控制过期时间

2、设置Cookie:

rep = HttpResponse(...) 或 rep = render(request, ...)
 
rep.set_cookie(key,value,...) # 设置cookie, 关闭浏览器即失效
rep.set_signed_cookie(key,value,salt='加密盐',...)
    参数:
        key,              键
        value='',         值
        max_age=None,     超时时间(s)
        expires=None,     超时时间(天)(IE requires expires, so set it if hasn't been already.)
        path='/',         Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
        domain=None,      Cookie生效的域名
        secure=False,     https传输(置成true)
        httponly=False    只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。如果通过js操作cookie,只能在js中设置和获取,在django中奶奶的无法获取。

<script src='/static/js/jquery.cookie.js'></script>
$.cookie("list_pager_num", 30,{ path: '/' });

实例:

urls:

urlpatterns = [
    url(r'^login/$', views.login, name='login'),
    url(r'^indexcookie/$', views.indexcookie),
]

views:

# 以全局变量代替数据库使用
user_dict = {
    'uson':{'pwd': 'akaedu'},
    'cohui':{'pwd': 'akaedu'},
}
def login(request):
    if request.method == 'GET':
        return render(request, 'day21-app03/login.html')
    elif request.method == 'POST':
        user = request.POST.get('username')
        pwd = request.POST.get('pwd')
        # 从字典获取用户
        dict = user_dict.get(user)
        if dict:
            print("已获取到用户信息")
            if pwd == dict['pwd']:
                print("密码正确")
                # return redirect('/uson/indexcookie/')
                res = redirect('/uson/indexcookie/')
                # res.set_cookie(user) #获取不到
                res.set_cookie('username123', user)
                # res.set_cookie('123', '123', httponly=True) #js无法抓取到
                return res
            else:
                print("密码错误")
                return render(request, 'day21-app03/login.html')
        else:
            print("未获取用户信息,请重新登录")
            redirect('/uson/login/')

def indexcookie(request):
    username = request.COOKIES.get('username123')
    if not username:
        return redirect('/uson/login/')
    return render(request, 'day21-app03/indexcookie.html', {'UserName': username})

login:

{% extends 'day21-app03/master.html' %}

{% block title %}用户登录{% endblock %}
{% block content1 %}
    <form action="{% url 'u-auth:login' %}" method="post">
        <input type="text" name="username" />
        <input type="password" name="pwd" />
        <input type="submit" value="登录" />
    </form>
{% endblock %}

indexcookie:

{% extends 'day21-app03/master.html' %}

{% block title %}成功登录{% endblock %}
{% block content1 %}
    欢迎登录:{{ UserName }}
{% endblock %}

Session

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

原理:

基于Cookie做用户验证时:敏感信息不适合放在cookie中
Cookie的优点是,把服务器的压力分散到用户的浏览器上。
如果不设置cookie的超时时间,默认在关闭浏览器就会清除cookie

a. Session原理及与Cookie的区别
	区别:
	Cookie是保存在用户浏览器端的键值对
	Session是保存在服务器端(可以保存在文件、缓存等)的键值对,只给客户端cookie一个随机字符串,交流取数据
	原理:
	1、生成随机字符串
	2、写到用户浏览器Cookie
	3、保存到Session中(Django默认是放入数据库)
	4、在随机字符串对应的字典中设置相关内容
	获取数据:
	1、从浏览器的Cookie中获取用户的随机字符串
	2、根据这个随机字符串获取session中对应的字典信息,作出下一步的判断
	
b. Session配置(缺少cache)

c. 示例:实现两周自动登陆
        - request.session.set_expiry(60*10)
        - SESSION_SAVE_EVERY_REQUEST = True

PS: cookie中不设置超时时间,则表示关闭浏览器自动清除

- session依赖于cookie

图文并茂,才完美

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存+数据库
  • 加密cookie

1、数据库Session

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
 
a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
     
    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,默认修改之后才保存(默认)
 
b. 使用
 
    def index(request):
        # 获取、设置、删除Session中数据
        request.session['k1']   # k1不存在,就会报错
        # 补充:views中,通过request传给前端,前端也可以获取session的数据{{request.session.username}}

        request.session.get('k1',None)  # k1不存在,返回None
        request.session['k1'] = 123  # 不存在,设置值,存在,更新值
        request.session.setdefault('k1',123) # 存在则不设置
        del request.session['k1'] # 删除某一条数据,删除不彻底。其他脏数据会存在服务器

        # 删除当前用户的所有Session数据
        request.session.delete("session_key")  # 通过下面方法获取session_key
        # 用户session的随机字符串
        request.session.session_key

        一般我们注销用:
        request.session.clear() # 实际上,内部就是封装了上面的两个步骤
 
        # 所有 键、值、键值对
        request.session.keys()
        request.session.values()
        request.session.items()
        request.session.iterkeys()
        request.session.itervalues()
        request.session.iteritems()
 
        # 将所有Session失效日期小于当前日期的数据删除(用户清除了cookie,又重新登录,生成新的随机字符串,原先的数据还存储在session的数据库中,如果不删除,即为脏数据)
        request.session.clear_expired()  # 在设定的时间后删除
 
        # 检查 用户session的随机字符串 在数据库中是否
        request.session.exists("session_key")
 
        # 设置超时时间(session的默认超时时间为2周,cookie:2周,既设置session也设置cookie),可以在下面的配置文件中设置默认操作(包括超时时间)
        request.session.set_expiry(value)
            * 如果value是个整数,session会在设置值后(秒数后)失效。
            * 如果value是个datatime或timedelta,session就会在这个时间后失效。
            * 如果value是0,用户关闭浏览器session就会失效。
            * 如果value是None,session会依赖全局session失效策略。

2、缓存Session

a. 配置 settings.py (通用配置)
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache,默认不支持redis缓存,可通过redis插件),此处别名依赖缓存的设置
    # SESSION_CACHE_ALIAS = 'uson_cache'   缓存的其他的配置
 
    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,默认修改之后才保存
 
b. 使用
 
    同上

3、文件Session

a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
 
 
    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,默认修改之后才保存
 
b. 使用
 
    同上

4、缓存+数据库Session

数据库用于做持久化,缓存用于提高效率
 
a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
 
b. 使用
 
    同上

5、加密cookie Session

a. 配置 settings.py
     
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
 
b. 使用
 
    同上

更多参考:猛击这里猛击这里

扩展:Session用户验证

def login(func):
    def wrap(request, *args, **kwargs):
        # 如果未登陆,跳转到指定页面
        if request.path == '/test/':
            return redirect('http://www.baidu.com')
        return func(request, *args, **kwargs)
    return wrap

实例:

settings:

# 设置以最后一次刷新时间作为起点,免登录
SESSION_SAVE_EVERY_REQUEST = True

urls:

url(r'^login$', views.login, name='login-session'),
url(r'^index$', views.index),
url(r'^logout', views.logout),

views:

user_dict = {
    'uson':{'pwd': 'akaedu'},
    'cohui':{'pwd': 'ys9213'},
}
def login(request):
    if request.method == 'GET':
        return render(request, 'day22-app04/login.html')
    elif request.method == 'POST':
        # (1)分解:from django.middleware.csrf import CsrfViewMiddleware
        # (2)找到:settings.CSRF_HEADER_NAME
        # (3)打印,看详情
        # from django.conf import settings
        # print(settings.CSRF_HEADER_NAME)
        # HTTP_X_CSRFTOKEN

        user = request.POST.get('username', None)
        pwd = request.POST.get('pwd', None)
        print(user, pwd)
        # 从字典获取用户
        dict = user_dict.get(user, None)
        print(dict)
        if dict:
            print("已获取到用户信息")
            if pwd == dict['pwd']:
                print("密码正确")

                # 设置session
                # (1)生成随机字符串
                # (2)写到用户浏览器cookie
                # (3)保存到session中(数据库中)
                # (4)在随机字符串对应的字典中设置相关内容
                request.session['username'] = user   # session设置值
                # (5)登录状态标识
                request.session['is_login'] = True  # session设置值

                # 10s免登录
                # if request.POST.get('rmb', None):
                if request.POST.get('rmb', None) == 1:
                    request.session.set_expiry(10) # 10s钟
                    # 其次,还要在设置中,设置以最后一次刷新时间作为起点

                return redirect('/cohui/index')
            else:
                print("密码错误")
                return redirect('/cohui/login')
        else:
            print("未获取用户信息,请重新登录")
            return redirect('/cohui/login')

def index(request):
    # (1)获取当前用户的随机字符串
    # (2)根据随机字符串获取对应信息
    # if request.session['is_login']:     # session获取值, 不存在直接报错,不建议使用
    if request.session.get('is_login', None):     # session获取值, 不存在, 返回None
        # return render(request, 'index.html', {'UserName': request.session['username']})
        return render(request, 'day22-app04/index.html') #request已经传给了前端,前端可以使用{{ request.session.username }}
    return redirect('/cohui/login')

# 注销
def logout(request):
    request.session.clear()
    return redirect('/cohui/login')

index:

{% extends 'day22-app04/master.html' %}

{% block title %}成功登录{% endblock %}
{% block content1 %}
    欢迎登录:<!--{{ UserName }}--> {{ request.session.username }}
    <a href="/cohui/logout">注销登录</a>
{% endblock %}

login:

{% extends 'day22-app04/master.html' %}

{% block title %}用户登录{% endblock %}
{% block content1 %}
    <form action="{% url 'login-session' %}" method="post">

        <!-- {{ csrf_token }} 一段字符串显示在前端-->
        {% csrf_token %}
        <!-- rTbu23QhlTd5Uj5CrY4FqUrG0WyuIJitICYzP3Aa7XQpvmdgo8BAb6J7BjFelZ5K -->

        <input type="text" name="username" />
        <input type="password" name="pwd" />

        <input type="checkbox" name="rmb" value="1" /> 10秒免登录
        <!-- 如果value不为0, 执行2周免登录条件 -->

        <input type="submit" value="登录" />  <!-- 表单方式提交 -->

        <input id="btn" type="button" value='提交' />  <!-- Ajax方式提交 -->
        <input id="btn2" type="button" value='提交2' />  <!-- Ajax方式提交 -->
    </form>
{% endblock %}
{% block js %}
    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        $(function (){
            //每个页面执行ajax之前,先执行:针对先前注释了csrf的办法:
            /*
            $.ajaxSetup({
               beforeSend: function (xhr, settings) {
                   // 包括了get,post...所有请求,一般使用下面的方法
                   xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                   //rTbu23QhlTd5Uj5CrY4FqUrG0WyuIJitICYzP3Aa7XQpvmdgo8BAb6J7BjFelZ5K
                   //每次的csrftoken是不一样的
               }
            });
            */
            //一般这样使用,对于get请求不用csrftoken,做个判断:
            function csrfSafeMethod(method) {
                // these HTTP methods do not require CSRF protection
                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); //test正则表达式
            }
            $.ajaxSetup({
               beforeSend: function (xhr, settings) {
                   // settings.type: 执行ajax之前,会把ajax里面的配置字典拿过来,在这里做判断
                   if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                       xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                   }
               }
            });

            $('#btn').click(function(){
                $.ajax({
                    'url': '/cohui/login',
                    //'type': 'post',
                    'type': 'POST',
                    'data': {'username': 'uson', 'pwd': 'akaedu'}, //开启了csrf, 没有随机字符串,报错xhr类型403错误

                    //headers: {'SSSSSSSSSSSSSSSS': 'uson'},
                    //headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function (data) {
                        //
                    }
                });
            });
            $('#btn2').click(function(){
                $.ajax({
                    'url': '/cohui/login',
                    //'type': 'post',
                    'type': 'POST',
                    'data': {'username': 'uson', 'pwd': 'akaedu'}, //开启了csrf, 没有随机字符串,报错xhr类型403错误

                    //headers: {'SSSSSSSSSSSSSSSS': 'uson'},
                    //headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function (data) {
                        //
                    }
                });
            });
        });
    </script>
{% endblock %}

防跨站请求伪造 CSRF

一、简介

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。

全局:

  中间件 django.middleware.csrf.CsrfViewMiddleware

局部:

  • @csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
  • @csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

注:from django.views.decorators.csrf import csrf_exempt,csrf_protect

二、应用

1、普通表单

veiw中设置返回值:
  return render_to_response('Account/Login.html',data,context_instance=RequestContext(request))  
     或者
     return render(request, 'xxx.html', data)
  
html中设置Token:
  {% csrf_token %}

2、Ajax

对于传统的form,可以通过表单的方式将token再次发送到服务端,而对于ajax的话,使用如下方式。

view.py

from django.template.context import RequestContext
  
def test(request):
    if request.method == 'POST':
        print request.POST
        return HttpResponse('ok')
    return  render_to_response('app01/test.html',context_instance=RequestContext(request))

html

<!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));
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    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>

更多:https://docs.djangoproject.com/en/dev/ref/csrf/#ajax

三、原理:

1、用户第一次请求过来,服务端不仅返回一大堆数据给用户,还偷偷地给了他一个字符串,这个字符串只有自己能反解;
2、当用户提交表单数据时,就必须带着这个随机字符串过来,否则不允许。
     一般体现在post请求,get请求没有限制。

分析:

方法一:form表单提交
表单里加上:{{ csrf_token }},前端显示的是的一段随机字符串
表单里加上:{% csrf_token %},随机字符串默认以隐藏的input形式存在

方法二:Ajax提交(没有csrf验证,前端提示403 xhr类型的错误)
分析:
1、找到settings中的csrf,对其分解得到(此settings非彼settings,此settings配置多得多):
 from django.middleware.csrf import CsrfViewMiddleware
2、查源码CsrfViewMiddleware:找到settings.CSRF_HEADER_NAME
3、from django.conf import settings
print(settings.CSRF_HEADER_NAME)==>HTTP_X_CSRFTOKEN
4、先注释csrf这一行代码,在Ajax中加入:
headers:{'kkkkkk': 123},提交数据;给POST.get()加上断点,前端提交请求;
我们可以在后端的request信息中,找到META请求数据,打开找到kkkkkk这一条数据,会发现:
存在形式是:HTPP_kkkkkk
那是因为,Django自动加上了HTTP_,所以:
4、把ajax请求头的数据修改为:
headers:{'X-CSRFtoken':$.cookie('csrftoken')},并取消注释csrf配置
这里:ajax请求头中,不能有‘_’下划线,会被识别为非法字符,可以使用'-'中划线。

注意:
如果之前,注释了csrf配置,那么现在取消注释,则所有的ajax的post请求都失效。怎么办?
方法1:
给每个ajax请求的header中加入:
headers:{'X-CSRFtoken':$.cookie('csrftoken')}
方法2:
原理:实际上,ajax执行时,在jquery内部,本质就是通过:
obj = XMLHTTPRequest()
obj.open()
obj.send()
来调用ajax请求。

方法:每个页面js写个框架:
$(function (){
    //在执行ajax之前,先执行它:针对先前注释了csrf的办法:
    /*
    $.ajaxSetup({
       beforeSend: function (xhr, settings) {
           // 包括了get,post...所有请求,一般使用第二种方法
           xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
           //rTbu23QhlTd5Uj5CrY4FqUrG0WyuIJitICYzP3Aa7XQpvmdgo8BAb6J7BjFelZ5K
           //每次的csrftoken是不一样的
       }
    });
    */
    //一般这样使用,对于get请求不用csrftoken,做个判断:
    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); //test正则表达式
    }
    $.ajaxSetup({
       beforeSend: function (xhr, settings) {
           // settings.type: 执行ajax之前,会把ajax里面的配置字典拿过来,在这里做判断
           if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
               xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
           }
       }
    });
});

装饰器

# 装饰器实现其他页面登录才能访问功能(即将验证代码装饰到类外面的嵌套高阶函数中)
FBV:
	def auth(func):
		def inner(reqeust,*args,**kwargs):
			v = reqeust.COOKIES.get('username111')
			if not v:
				return redirect('/login/')
			return func(reqeust, *args,**kwargs)
		return inner

CBV:
	from django import views
	from django.utils.decorators import method_decorator

	@method_decorator(auth,name='dispatch')  # 装饰所有方法
	class Order(views.View):

		# @method_decorator(auth)   # 装饰所有方法
		# def dispatch(self, request, *args, **kwargs):
		#     return super(Order,self).dispatch(request, *args, **kwargs)

		# @method_decorator(auth)  # 仅装饰该方法
		def get(self,reqeust):
			v = reqeust.COOKIES.get('username111')
			return render(reqeust,'index.html',{'current_user': v})

		def post(self,reqeust):
			v = reqeust.COOKIES.get('username111')
			return render(reqeust,'index.html',{'current_user': v})
注意事项(通过作业发现):
1、装饰器函数的参数(调用和返回)*args, **kwargs,必须包含dispatch/get/post/的现有参数(即如有nid,就必须将nid放在*args参数之后,nid参数由**kwargs接收)
2、url中参数可以比方法(get)中参数少,但不能多
3、redirect('/完整路径') 否则无效

缓存

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。其他Web框架都不支持缓存功能。

Django中提供了6种缓存方式(只是引擎不同):

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)   这两种缓存只是调用模块不同而已
  • Memcache缓存(pylibmc模块)

1、配置

a、开发调试

# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',     # 引擎
                'TIMEOUT': 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                'OPTIONS':{
                    'MAX_ENTRIES': 300,                                       # 最大缓存个数(默认300,即300种不同的缓存)
                    'CULL_FREQUENCY': 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3,即1/3)
                },
                'KEY_PREFIX': '',                                             # 缓存key的前缀(默认空)
                'VERSION': 1,                                                 # 缓存key的版本(默认1)
                'KEY_FUNCTION' 函数名                                          # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
            }
        }

    # 自定义key
    def default_key_func(key, key_prefix, version):
        """
        Default function to generate keys.

        Constructs the key used by all other methods. By default it prepends
        the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
        function with custom key making behavior.
        """
        return '%s:%s:%s' % (key_prefix, version, key)

    def get_key_func(key_func):
        """
        Function to decide which key function to use.

        Defaults to ``default_key_func``.
        """
        if key_func is not None:
            if callable(key_func):
                return key_func
            else:
                return import_string(key_func)
        return default_key_func

b、内存

# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                'LOCATION': 'unique-snowflake',  # 值必须唯一,因为会成为全局变量
            }
        }

    # 注:其他配置同开发调试版本

c、文件

# 此缓存将内容保存至文件
    # 配置:

        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/var/tmp/django_cache',
            }
        }
    # 注:其他配置同开发调试版本

d、数据库

# 此缓存将内容保存至数据库

    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
                'LOCATION': 'my_cache_table', # 数据库表
            }
        }

    # 注:执行创建表命令 python manage.py createcachetable

e、Memcache缓存(python-memcached模块)

# 此缓存使用python-memcached模块连接memcache

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': 'unix:/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }

f、Memcache缓存(pylibmc模块)

# 此缓存使用pylibmc模块连接memcache
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '/tmp/memcached.sock',
        }
    }   

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': [  # 集群分布式
                '172.19.26.240:11211',   # 也可以加权重: ('172.19.26.240:11211', 2),  出现次数:2
                '172.19.26.242:11211',   # 也可以加权重: ('172.19.26.242:11211', 10), 出现次数:10
            ]
        }
    }

g. Redis缓存(依赖:pip3 install django-redis)

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
            # "PASSWORD": "密码",
        }
    }
}

memcache内部处理原理:

分布式集群模式:
(1)不加权重的情况下:
'172.19.26.240:11211',   
'172.19.26.242:11211',   

1)先将字符串(key)转成数字;
2)再将数字/个数,求余数
以两个为例,余数为0或1,
如果余数为0:缓存在序号为0机器上;
如果余数为1:缓存在序号为1机器上;

(2)加权重的情况下:
# 也可以加权重: ('172.19.26.242:11211', 10), 出现次数:10
# 也可以加权重: ('172.19.26.240:11211', 2),  出现次数:2

1)将权重相加:10+2=12
2)int(key)/权重和,求余数,出现在权重高的机器上次数就会多。

安装:
python操作Memcached使用Python-memcached模块
下载安装:https://pypi.python.org/pypi/python-memcached

视图中链接与操作

from django_redis import get_redis_connection
conn = get_redis_connection("default")

2、应用

a. 全站使用(用得少)

使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

    MIDDLEWARE = [
        'django.middleware.cache.UpdateCacheMiddleware',  # 只有process_response函数
        # 其他中间件...
        'django.middleware.cache.FetchFromCacheMiddleware', # 只有process_request函数
    ] 

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = ""
    CACHE_MIDDLEWARE_KEY_PREFIX = ""

过程:
1、请求第一次到达,会先判断缓存区有没有数据,如果没有数据,会正常执行,经过:'django.middleware.cache.FetchFromCacheMiddleware'中间件;
如果,缓存区有数据,不会到达views视图函数,而是执行同一中间件的process_response函数,一步一步返回;
2、到达'django.middleware.cache.UpdateCacheMiddleware'中间件:
会有一步判断,判断缓存区有没有数据,没有就缓存。

b. 单独视图缓存

方式一:
        from django.views.decorators.cache import cache_page

        @cache_page(60 * 15)
        def my_view(request):
            ...

    方式二:
        from django.views.decorators.cache import cache_page

        urlpatterns = [
            url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
        ]

c、局部视图使用

a. 引入TemplateTag

        {% load cache %}

    b. 使用缓存

        {% cache 5000 缓存key %}
            缓存内容
        {% endcache %}

小实例-views:

#################################缓存示例##################################
# (1)settings配置缓存
# (2)url路由映射
# (3)views函数视图、模板渲染
# (4)使用缓存 - 装饰器

# 方法一:views视图缓存:views, 方法二:urls
from django.views.decorators.cache import cache_page
@cache_page(5) # 缓存5s,也可以(60*60)=1h
def cache(request):
    import time
    ctime = time.time()
    return render(request, 'day22-app04/cache.html', {'ctime':ctime})

cache.html

{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>缓存</title>
</head>
<body>
    <h1>{{ ctime }}</h1>
    <h1>{{ ctime }}</h1>

    <!-- 局部模块缓存:缓存5s,key: c1 ===>内部存储:前缀:版本:key -->
    {% cache 5 c1 %}    
    <h1>{{ ctime }}</h1>
    {% endcache %}
</body>
</html>

settings配置:

#################以文件缓存为示例###############
# CACHES = {
#             'default': {
#                 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
#                 'LOCATION': 'app04/cache',  # 文件路径
#             }
#         }

全局缓存原理:

更多:猛击这里

Memcached:点击跳转

信号

Django中提供了“信号调度”,用于在框架执行操作时解耦(Django给一些对象、方法的前后各提供了钩子,供我们自定义操作)。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接收者。

1、Django内置信号

Model signals
# obj = models.Userinfo(username='uson')  构造方法
# obj.save()   对象保存
# ...
    pre_init                    # django的modal执行其构造方法前,自动触发
    post_init                   # django的modal执行其构造方法后,自动触发

    pre_save                    # django的modal对象保存前,自动触发
    post_save                   # django的modal对象保存后,自动触发
    pre_delete                  # django的modal对象删除前,自动触发
    post_delete                 # django的modal对象删除后,自动触发

    m2m_changed                 # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发

    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
                                          即加载modal数据到内存
Management signals
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发

Request/response signals
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发

Test signals
    setting_changed             # 使用test测试修改配置文件时,自动触发
    template_rendered           # 使用test测试渲染模板时,自动触发

Database Wrappers
    connection_created          # 创建数据库连接时,自动触发

对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数(2种方式):

from django.core.signals import request_finished
from django.core.signals import request_started
from django.core.signals import got_request_exception

from django.db.models.signals import class_prepared
from django.db.models.signals import pre_init, post_init
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import pre_delete, post_delete
from django.db.models.signals import m2m_changed
from django.db.models.signals import pre_migrate, post_migrate

from django.test.signals import setting_changed
from django.test.signals import template_rendered

from django.db.backends.signals import connection_created

def callback(sender, **kwargs):
    print("xxoo_callback")
    print(sender,kwargs)

xxoo.connect(callback)   # 注册信号
# xxoo指上述导入的内容:request_finished...

理解:
1、利用相应的内置信号,在信号文件里创建需要触发的事件(函数)
2、在工程目录的同名目录下的__init__.py文件中,导入信号文件:import...
3、这样信号事件就会在相应的钩子位置触发执行。
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)   # 注册信号
def my_callback(sender, **kwargs):
    print("Request finished!")

内置信号views:

from app04 import models
def signal(request):
    obj = models.Userinfo(username='uson')   # 自动触发信号
    obj.save()
    print("model信号")
    return HttpResponse('OK')
# 执行结果》》
# model信号
# Request finished!

2、自定义信号

a. 定义信号

import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

b. 注册信号

def callback(sender, **kwargs):
    print("callback")
    print(sender,kwargs)

 pizza_done.connect(callback)  # 注册信号

c. 触发信号(views)

from 路径 import pizza_done
pizza_done.send(sender='uson',toppings=123, size=456)

###############自定义信号###############
# views中触发信号
from app04.sg import pizza_done
pizza_done.send(sender='uson', toppings=123, size=456)
def my_callback(request):
    print("Request uson!")
    return HttpResponse('ok')

小结:

由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。

更多:猛击这里

后记

obj = models.Userinfo.objects.get(name='uson')  #  对象(单条数据) 不存在,报错,try
obj = models.Userinfo.objects.filter(name='uson').first()   对象或None,不存在不报错
print(obj.query) #可以查看该句的SQL语句:SELECT ...
print(obj.count()) 对象列表个数 <QuerySet [<Userinfo: Userinfo object (2)>, <Userinfo: Userinfo object (3)>]>
sep="\t" 表示以tab(制表符)为分隔符

__跨表操作:

values、values_list跨表操作(通过__):
	host_tuple = models.Host.objects.filter(nid__gt=0).values('nid', 'b__caption')
	for row in host_tuple:
		print(row[nid], row[b__caption])  python中只要是对象,就可以一直.下去取值
	host_tuple = models.Host.objects.filter(nid__gt=0).values_list('nid', 'b__caption')
	for row in host_tuple:
		print(row[0], row[1])   对于字典/元组,在html中均是以.取值,跨表字符串形式,在python/html中都是以__取值。

修改数据表结构时会遇到的问题:

如果增加列,可以通过以下3中方法:

(1)直接在命令输入的返回提示后,选择方法1,输入指定该列的默认值;

(2)增加列添加null=True,表示可为空;

(3)增加列添加default='xx',表示设置默认值。

猜你喜欢

转载自www.cnblogs.com/uson/p/11597381.html