Django基础 :session会话、认证系统、内容分页、中间件

来源:互联网 发布:杭州单片机工程师招聘 编辑:程序博客网 时间:2024/06/05 03:50

一、Cookies & Session


Cookies:

由于http协议是无状态的在一次请求和下一次请求之间没有任何状态保持我们无法根据请求的任何方面来识别来自同一人的连续请求。然而浏览器的开发者在很早的时候就已经意识到 HTTP 的无状态会对Web开发者带来很大的问题于是(cookies)应运而生。 cookies 是浏览器为 Web 服务器存储的一小段信息。 每次浏览器从某个服务器请求页面时它向服务器回送之前收到的cookies。


开发者可以通过在用户浏览器中设置一个cookies用来保存某一个唯一标识符在用户下次请求时通过读取之前存入的标识信息来识别是否为同一个用户。 因此还可以在cookies中存入一些其他标识用于定制用户的个性化需求。如:“背景颜色、字体大小、显示格式等等 ...”


Cookies的不足之处:

由于http协议是明文的在传输过程中非常容易被嗅探攻击抓取到因此在Cookies里面要绝对避免存储敏感信息意味着不应该使用cookie存储任何敏感信息。


同时也要避免在cookies中存储可能会被篡改的敏感数据。 在cookies中存储 IsLoggedIn=1 以标识用户已经登录。 犯这类错误的站点数量多的令人难以置信 绕过这些网站的安全系统也是易如反掌。


Session:

session是存储在服务器端的一段数据与cookies配合工作有了session可以弥补cookies的不足每个session都由一个随机的32字节哈希串来标识并存储于cookie中。


看图理解:

wKioL1dZZDvgri9NAABGltMYAKc672.png

它们的工作流程:


新用户

1、浏览器向服务器发起请求  

2、服务器响应返回一个登陆页面让用户登录

3、在用户登录成功后服务器将随机生成一个session-id会话id将其最为一个key对应用户的登陆数据保存在服务器的某个位置再将session-id保存到用户浏览器的cookies中。



老用户

1、浏览器向服务器发起请求  

2、检查浏览器缓存中是否存有该网站域名对应的cookies并且没有过期

  • 如果有域名对应的cookies则带上cookies向服务器发起请求

  • 如果没有则直接向服务器发起请求

3、服务器端检查浏览器传过来的cookies中的session-id和服务器保存的session是否有对应的匹配并且没有过期。

  • 如果有匹配并且没有过期则被认为该会话有效是已经登陆过的合法用户程序直接跳过用户登录页面返回已登陆的后台展示页面。

  • 如果没有匹配或者已经过期则被认为该会话无效是没有登陆的未授权用户程序返回登陆页面让用户重新登陆授权。在用户再次登陆成功后服务器会重新建立会话更新seesion



二、Cookies & Session 实战应用


写入Cookies

def logintest(request):    if request.method == 'POST':        username = request.POST.get('username')        password = request.POST.get('passwd')        print(username)        print(password)        if username == 'tuchao' and password == '123456':            response = HttpResponse("Welcome You Login Success")            # 写入cookies 设置key为logindvalue为True            response.set_cookie("logind",True)            return response    f = open("templates/logintest.html",'r')    html = f.read()    return HttpResponse(html)


还可以给 response.set_cookie() 传递一些可选的参数来控制cookie的行为

max_age# cookie需要延续的时间以秒为单位如果参数是'None'这个cookie会延续到浏览器关闭为止。 expires# cookie失效的实际日期/时间。它的格式必须是DD-Mth-YY HH:MM:SS GMT 如果给出了这个参数它会覆盖 max_age 参数。 path# cookie生效的路径前缀。 浏览器只会把cookie回传给带有该路径的页面这样你可以避免将cookies传给站点中其他的顶层页面。 # 例如: response.set_cookie("logind",True,path='/home')

读取Cookies

def logintest(request):    if request.method == 'POST':        username = request.POST.get('username')        password = request.POST.get('passwd')        print(username)        print(password)        if username == 'tuchao' and password == '123456':            response = HttpResponse("Welcome You Login Success")            # 设置cookies            response.set_cookie("logind",True)            response.set_cookie("logdata",'hello world')            return response    f = open("templates/logintest.html",'r')    html = f.read()    # 读取cookies并打印出来。    print request.COOKIES['logdata']    return HttpResponse(html)


如何打开Session功能

1、编辑settings.py文件找到 MIDDLEWARE_CLASSES 确保其中包含

'django.contrib.sessions.middleware.SessionMiddleware' 


2、确认 INSTALLED_APPS中 有 'django.contrib.sessions' 


如果项目是用startproject 创建的配置文件中默认都已经加载了这些可以直接使用seeion功能


使用Session

SessionMiddleware 激活后每个传给视图(view)函数的第一个参数``HttpRequest`` 对象都有一个 session 属性这是一个字典型的对象。 你可以象用普通字典一样来用它。 例如

# 设置一对session数据 key:valuerequest.session["color"] = "blue" # 通过key获取一个seesion值fav_color = request.session["color"] # 清除一对session数据 key:valuedel request.session["color"] # 判断该key在当前session里是否存在if "color" in request.session:

下面是一个通过seesion做登陆会话保持的简单示例代码:

# 登陆功能函数def logintest(request):    if request.method == 'POST':        username = request.POST.get('username')        password = request.POST.get('passwd')        if username == 'tuchao' and password == '123456':            # 当登陆成功后记录 session            request.session['logined'] = 'yes'            return redirect('/ops01/adminindex')     f = open("templates/logintest.html",'r')    html = f.read()    return HttpResponse(html) # 后台管理页入口函数    def adminindex(request):    # 取得session值    is_login = request.session.get('logined',None)    # 如果session不存在则返回登陆页面    if not is_login:        return redirect('/ops01/logintest/')    return render_to_response('admin-index.html',{}) # 登录退出函数    def logout(request):    is_login = request.session.get('logined',None)    if is_login:        del request.session['logined']        return redirect('/ops01/logintest')    return HttpResponse('404')

常用于控制Seesion行为的设置:


SESSION_SAVE_EVERY_REQUEST = False

在默认情况下Django只会在session发生变化的时候才会存入数据库比如说字典赋值或删除。将此参数设置为True来改变这一默认行为。如果设置为True的话Django会在每次收到请求的时候保存session即使没发生变化。


SESSION_EXPIRE_AT_BROWSER_CLOSE = False

一般情况下Cookie可以设置过期时间这浏览器就知道什么时候删除Cookie当没有给Cookie设置过期时间的时候可以通过将此参数设置为True来控制当用户关闭浏览器时session失效。 此参数的默认值为False


SESSION_COOKIE_AGE = 60

用于设置会话的有效时长单位秒默认值为两周即:1,209,600 秒。如果不想用户每次打开浏览器都要登陆的话,可以设置 SESSION_EXPIRE_AT_BROWSER_CLOSE = False 然后通过设置 SESSION_COOKIE_AGE 来控制会话保持的时长。


SESSION_COOKIE_NAME = 'sessionid'

用于设置Cookie中保持的会话id名称它可以是任意字符串Django默认为"sessionid"。



三、Django Authentication


打开 Authentication


1、将 'django.contrib.auth' 放在你的 INSTALLED_APPS 设置中然后同步生成数据库表。


2、确认 SessionMiddleware 后面的 MIDDLEWARE_CLASSES 设置中包含 

'django.contrib.auth.middleware.AuthenticationMiddleware' SessionMiddleware。



User对象

位于'django.contrib.auth.models'模块中有两个多对多的属性分别是groups和user_permissions。


常用属性描述username必需的不能多于30个字符。 仅用字母数字式字符字母、数字和下划线。email可选。 邮件地址
password必需的。 密码的哈希值Django不储存原始密码。is_staff判断是否用户可以登录进入admin site。is_active用来判断该用户是否是可用激活状态。is_superuser布尔值 标识用户是否拥有所有权限无需显式地权限分配定义。date_joined账号被创建的日期时间 当账号被创建时它被默认设置为当前的日期/时间。


对象方法

from django.contrib.auth.models import User,Permission,Groupfrom django.contrib import auth  # 这是一个分辨用户是否已经通过登陆认证的方法它并不意味着任何权限也不检查用户是否仍是活动的只是表明该用户提供了正确的username和password。is_authenticated() # 返回一个字符串是first_name和last_name中间加一个空格组成。get_full_name(): # 调用该方法时候传入一个明文密码该方法会进行hash转换。该方法调用之后并不会保存User对象。set_password(raw_password): # 将用户设置为没有密码的状态。调用该方法后check_password()方法将会永远返回false。但是如果调用set_password()方法重新设置密码后该方法将会失效has_usable_password()也会返回True。set_unusable_password() # 在调用set_unusable_password()方法之后该方法返回False正常情况下返回True。has_usable_password() # 返回该用户通过组所拥有的许可字符串列表每一个代表一个许可。obj如果指定将会返回关于该对象的许可而不是模型。get_group_permissions(obj=None) # 返回该用户所拥有的所有的许可包括通过组的和通过用户赋予的许可。get_all_permissions() # 如果用户有传入的perm则返回True。perm可以是一个格式为'<app label>.<permission codename>'的字符串。如果User对象为inactive该方法永远返回False。和前面一样如果传入obj则判断该用户对于这个对象是否有这个许可。has_perm(perm,obj=None) # 和has_perm一样不同的地方是第一个参数是一个perm列表只有用户拥有传入的每一个perm返回值才是True。has_perms(perm_list,obj=None): # 发送一封邮件给这个用户依靠的当然是该用户的email属性。如果from_email不提供的话Django会使用settings中的DEFAULT_FROM_EMAIL发送。email_user(subject,message,from_email=None) # 返回一个和Site相关的profile对象用来存储额外的用户信息。这个返回值会在另一片博文中详细描述。get_profile()

常用的对象方法使用示例

# ------- User ---------from django.contrib.auth.models import User,Permission,Groupfrom django.contrib import auth # 新建一个用户u1 = User.objects.create_user('tuchao','tuchaong@mail.com','123456')u1.save() # 获取用户名为tuchao的User对象u1 = User.objects.get(username='tuchao')  # -------- 删除一个用户 -------- # 1、获取需要被删除的用户对象u2 = User.objects.get(id=4) # 2、执行删除操作u2.delete()  # -------- Group 用户和组 ---------- # 创建组Group.objects.create(name='peiyin')Group.objects.create(name='color') # 获取已有组的Group对象g = Group.objects.get(id=1) # 查看组名g.nameOut[11]:'peiyin' # 添加一个用户到组u1 = User.objects.get(username='tuchao')u1.groups.add(g) # 查看用户添加的组情况u1.groups.all()Out[11]: [<Group: peiyin>] # 再添加'color'组g2 = Group.objects.get(id=2)u1.groups.add(g2) u1.groups.all()Out[17]: [<Group: color>, <Group: peiyin>] # 给用户设置一个组列表g = Group.objects.get(id=1)g2 = Group.objects.create(name='color')g3 = Group.objects.create(name='backend') glist = []glist.append(g)glist.append(g2)glist.append(g3) u1.groups = glist #  在组里添加 'hello' 用户u2 = User.objects.create_user('hello','hello@mail.com','123456')g.user_set.add(u2) # 查看该组下的用户In [21]: g.user_set.all()Out[21]: [<User: tuchao>, <User: hello>] # 删除组(注意,这里不管是执行删除用户还是删除组或者权限,都会把该记录相关的关系一并删除)g2.delete()   # ----------  Permission 权限 -------------from django.contrib.auth.models Permission # 用户权限管理 # 添加用户权限,比如给 'tuchao' 用户添加可以给hostandgroup表增加记录的权限。u1 = User.objects.get(username='tuchao')p = Permission.objects.get(id=25)# 该方法接受一个Permission对象为参数,用于给用户添加权限u1.user_permissions.add(p) # 给用户添加多条权限u1.user_permissions.add(p,p2,p3) # 查看用户的所有权限u1.get_all_permissions()Out[8]: {u'ops01.add_hostandgroup'} # 验证用户权限(用于校验用户是否有该权限,有返回True,没有返回 False)u1.has_perm('ops01.add_hostandgroup') # 验证用户的权限列表,接收一个列表为参数,如果用户拥有该列表中的所有权限则返回True 否则返回Falsealist = []alist.append('ops01.add_hostandgroup')alist.append('ops01.change_hostandgroup')alist.append('ops01.delete_hostandgroup')u1.has_perms(alist) # 删除用户权限u1.user_permissions.remove(p)  # 组权限管理 # 添加组权限g = Group.objects.get(id=1)p = Permission.objects.get(id=25)g.permissions.add(p) # 添加多条组权限g.permissions.add(p1,p2) # 给该组设置一个权限列表p = Permission.objects.get(id=25)p1 = Permission.objects.get(id=26)p2 = Permission.objects.get(id=27) plist = []plist.append(p)plist.append(p1)plist.append(p2) g.permissions = plist # 删除组权限g.permissions.remove(p) # 删除多条组权限g.permissions.remove(p1,p2) # 清空组权限g.permissions.clear() # 查看组的所有权限g.permissions.all()Out[42]: [<Permission: ops01 | user | Can add user>, <Permission: ops01 | user | Can change user>]


自定义权限

当我们在django中安装好了auth应用之后,Django就会为每一个你安装的app中的Model创建三个权限:add , change , delete ;  相应的数据,就是在你执行python manage.py makemigrations python manage.py migrate 之后插入到数据库中的。


假设你给应用创建了一个模型teeuser,Django就会在权限表(auth_permission)插入三条数据,表示默认创建的权限,如图:


wKioL1dpGnryX--sAAAObOoEI10207.png


除了默认权限我们也可以自定义一些权限。就是在Model类的meta属性中添加permissions定义。比方说,创建了一个模型类叫做Discussion,我们可以创建几个权限来对这个模型的权限进行控制,控制某些人可以发起讨论、发起回复,关闭讨论。


class Discussion(models.Model):    ...    class Meta:        permissions = (            ("open_discussion", "Can create a discussion"),            ("reply_discussion", "Can reply discussion"),            ("close_discussion", "Can remove a discussion by setting its status as closed"),        )

然后执行指令:makemigrations 、migrate 这样数据库就生成这新加的权限了。


刚刚是通过预定义的方式,然后同步数据库来添加自定义权限,毕竟在某些场景下有些不便。


现在我们看看Permission模型如何通过编程的方式,动态的添加自定义权限。


所属模块:django.contrib.auth.models


属性:

    1、name:权限描述

    2、codename:权限名称,用于标识一个权限。

    3、content_type:Model在权限里的ID,用于标识同一个Model中的权限。同一个Model中的权限 content_type 值一样。


示例代码:

from django.contrib.auth.models import auth,User,Permission,Group,ContentType # app_label 对应Django的APP名称,model 对应APP中的数据库model名称。 content_type = ContentType.objects.get(app_label='ops01',model='teeuser')permission = Permission.objects.create(codename='can_put_image',name='can put image file',content_type=content_type)


四、跨站请求伪造


Django为用户提供了防止跨站请求伪造的功能,通过 'django.middleware.csrf.CsrfViewMiddleware'来实现。


对于Django中的跨站请求伪造又分为全局和局部:


全局设置


在settings中打开 'django.middleware.csrf.CsrfViewMiddleware' 默认表示所有的函数都需要进行跨站请求伪造验证。


局部设置(可以通过装饰器来控制)


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

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

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


不同场景的应用实战


1、普通表单


view.py

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

login.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title></title></head><body> <!-- csrf 获取服务器返回的token -->{% csrf_token %} <form action="/ops01/login/" method="post">    <input type="submit" value="登 录" ></form>     </body></html>

2、Ajax

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

view.py

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

login.html

<!DOCTYPE html><html lang="en"><head>   <meta charset="UTF-8">   <title>后台管理</title>   <link rel="stylesheet" type="text/css" href="/static/style.css">    <script src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>    <script src="/static/assets/js/jquery.cookie.js"></script> </head><body><script type="text/javascript"><!--  csrf 部分  -->{% csrf_token %} 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);            }        }    }); <!--  end --> <!-- Ajax 请求部分(略)--> $(function(){         $('#denglu').click(function(){         $.ajax({        url:'/ops01/login/',        type:'POST',        data:{username:usernameval},                 success:function(arg){                 },                 error:function(){                 }              });         }) })  </script></body></html>


五、Django中间件


Django里的中间件(middleware),是一个类,在请求发起到响应执行结束,Django会根据自己的顺序执行中间件的各个部分。


在 setting 文件中的 MIDDLEWARE_CLASSES 其中每一项都是一个中间件:

wKioL1eYVf6gRaSZAABm-4M39KM235.png

中间件中可以定义的四个方法 :

  • process_request(self,request) 

  • process_view(self, request, callback, callback_args, callback_kwargs)

  • process_exception(self, request, exception)

  • process_response(self, request, response)


执行顺序(图)

wKioL1eYVkvT1pLOAAEG4gChVsk153.png


方法的返回值可以是None和HttpResonse对象,如果是None,则继续按照django定义的规则向下执行,如果是HttpResonse对象,则直接将该对象返回给用户。


自定义中间件

1、创建中间件类

mymiddleware.py

from django.http.response import HttpResponse class mydefinedmiddle(object):    # 请求到来时执行   def process_request(self,request):      print '1 process_request'             # view.py 之前执行   def process_view(self, request, callback, callback_args, callback_kwargs):      print '2 process_view'    # view.py 出错时执行   def process_exception(self, request, exception):      print '3 process_exception'    # 返回响应时执行      def process_response(self, request, response):      print '4 process_response'      return response


2、注册中间件

settings.py

MIDDLEWARE_CLASSES = [    'django.middleware.security.SecurityMiddleware',    'django.contrib.sessions.middleware.SessionMiddleware',    'django.middleware.common.CommonMiddleware',    'django.middleware.csrf.CsrfViewMiddleware',    'django.contrib.auth.middleware.AuthenticationMiddleware',    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',    'django.contrib.messages.middleware.MessageMiddleware',    'django.middleware.clickjacking.XFrameOptionsMiddleware',    'ops01.middledir.mymiddleware.mydefinedmiddle',]


六、实战开发


需求、开发一个主机列表页,支持分页功能,并且可以在页面自定义每页显示数据的条数。


新建一个模块文件,用来保存一些分页算法。

html_paging.py

#!/usr/local/python27/bin/python2.7# coding=utf8# noinspection PyUnresolvedReferencesfrom django.utils.safestring import mark_safe  class calculatepage:    def __init__(self,count,page,per_item=5):        self.count = count        self.page = page        self.per_item = per_item     @property    def startpoint(self):        self.startpoint = (self.page-1)*self.per_item        return self.startpoint     @property    def endpoint(self):        return self.page*self.per_item   # 这个函数用于求分页的页数    @property    def pages(self):         # 通过把数据总条数和每页展示的条数,整除求余数,得出需要的页数和剩余的数据条数        sum = divmod(self.count,self.per_item)         # 当为0时表示每页展示的数据条数被整除,表示不需要多加一个分页用来展示剩余的数据        if sum[1] == 0:            pages = sum[0]        else:            pages = int(sum[0]+1)        return pages def Pager(page,pages):    pagelist = []     first_page = "<a href='/ops01/hostlist/%d'>首页</a>" %(1)    pagelist.append(first_page)   # 计算上一页位置    if page == 1:        lastpage = "<a href='/ops01/hostlist/%d'>上一页</a>" %(1)    else:        lastpage = "<a href='/ops01/hostlist/%d'>上一页</a>" %(page-1)     pagelist.append(lastpage)     # 计算分页展示    if  pages <= 11:        beginpage = 0        lastpage = pages    else:        if page <= 6:            beginpage = 0            lastpage = 11        elif page+5 > pages:            beginpage = page-6            lastpage = pages        else:            beginpage = page-6            lastpage = page+5     for i in range(beginpage,lastpage):        if page == i+1:            a_html = "<a style='color:red' href='/ops01/hostlist/%d'>%d</a>" %(i+1,i+1)        else:            a_html = "<a href='/ops01/hostlist/%d'>%d</a>" %(i+1,i+1)        pagelist.append(a_html)         # 计算下一页位置    if page == pages:        nextpage = "<a href='/ops01/hostlist/%d'>下一页</a>" %(pages)    else:        nextpage = "<a href='/ops01/hostlist/%d'>下一页</a>" %(page+1)    pagelist.append(nextpage)    end_page = "<a href='/ops01/hostlist/%d'>尾页</a>" %(pages)    pagelist.append(end_page)     # 将列表中的数据连接起来,并且通过mark_safe方法,让数据可以被浏览器解释为html    page_html = mark_safe(''.join(pagelist))    return page_html

功能代码

view.py

from django.shortcuts import render_to_response,redirectfrom ops01 import html_paging def hostlist(request,page): # 获取用户cookies中自定义的展示条数    per_item = request.COOKIES.get('showlinenum',5)    per_item = int(per_item)    page = int(page)    cursor = connection.cursor()    count = cursor.execute("select hid,othername,hostname,inet_ntoa(wanip),inet_ntoa(lanip),hardconfig from host;")    data = cursor.fetchall()         # 调用前面写的分页算法类,实例化    pagecal = html_paging.calculatepage(count,page,per_item)    datalist=[]    for i in data:        dictob={}        dictob['hid'] = i[0]        dictob['othername'] = i[1]        dictob['hostname'] = i[2]        dictob['wanip'] = i[3]        dictob['lanip'] = i[4]        dictob['hardconfig'] = i[5]        datalist.append(dictob)    # 由于该类方法使用了@property 装饰器,所有这里调用不需要写()     showlist = datalist[pagecal.startpoint:pagecal.endpoint]    page_html = html_paging.Pager(page,pagecal.pages)    response = render_to_response('host-list.html',{'list':showlist,'per_page':page_html})    return response


前端展示

host-list.html

{% extends "masterplate.html" %} {% block title %}主机列表{% endblock %} {% block content %}    <div class="admin-content-hostlist">    <div class="am-cf am-padding">      <div class="am-fl am-cf"><strong class="am-text-primary am-text-lg">主机列表</strong> / <small>Host information</small></div>    </div>     <table border="1" class="hovertable">         <tr>         <th>主机ID</th><th>主机名</th><th>所属业务</th><th>内网IP</th><th>外网IP</th><th>配置信息</th>         </tr>         {% for i in list %}         <tr onmouseover="this.style.backgroundColor='#ffff66';" onmouseout="this.style.backgroundColor='#d4e3e5';">            <td>{{ i.hid }}</td>            <td>{{ i.hostname }}</td>            <td>{{ i.othername }}</td>            <td>{{ i.wanip }}</td>            <td>{{ i.lanip }}</td>            <td>{{ i.hardconfig }}</td>        </tr>         {% endfor %}     </table>    <div>        <select id='se1' onchange="changePageItem(this)">            <option value="10">10</option>            <option value="30">30</option>            <option value="50">50</option>            <option value="100">100</option>        </select>    </div>     <div class="hpages">    {{ per_page }}    </div>    </div> <script type="text/javascript">     $(function(){        var values = $.cookie("showlinenum");        if(values){            $('#se1').val(values);        }    });     function changePageItem(arg){        //创建或者修改cookies的值,path 用于定义该cookies的作用范围        var values = $(arg).val()        $.cookie('showlinenum',values,{path:'/'});    }</script>{% endblock %}


0 0