一、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中。
看圖理解:
它們的工作流程:
新用戶
1、瀏覽器向服務器發起請求
2、服務器響應返回一個登陸頁面讓用戶登錄
3、在用戶登錄成功後服務器將隨機生成一個session-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:value request.session["color"] = "blue" # 通過key獲取一個seesion值 fav_color = request.session["color"] # 清除一對session數據 key:value del 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個字符。 僅用字母數字式字符字母、數字和下劃線。 |
可選。 郵件地址 | |
password | 必需的。 密碼的哈希值Django不儲存原始密碼。 |
is_staff | 判斷是否用戶可以登錄進入admin site。 |
is_active | 用來判斷該用戶是否是可用激活狀態。 |
is_superuser | 布爾值 標識用戶是否擁有所有權限無需顯式地權限分配定義。 |
date_joined | 賬號被創建的日期時間 當賬號被創建時它被默認設置爲當前的日期/時間。 |
對象方法
from django.contrib.auth.models import User,Permission,Group from 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,Group from django.contrib import auth # 新建一個用戶 u1 = User.objects.create_user('tuchao','[email protected]','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.name Out[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','[email protected]','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 否則返回False alist = [] 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)插入三條數據,表示默認創建的權限,如圖:
除了默認權限我們也可以自定義一些權限。就是在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 其中每一項都是一箇中間件:
中間件中可以定義的四個方法 :
process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)
執行順序(圖)
方法的返回值可以是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 PyUnresolvedReferences from 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,redirect from 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 %}