設置cookie
HTTP協議是無狀態的,在一次請求響應結束后,服務器不會留下關於客戶端狀態的信息。但是對於某些web程序來說,客戶端的信息有必要被記住,比如用戶的登錄狀態,這樣就可以根據用戶的狀態來返回不同的響應。為了解決這個問題就有cookie技術 。cookie技術通過在請求和響應報文中添加cookie數據來保存客戶端的狀態信息。
cookie值web服務器為了存儲用戶信息而保存在瀏覽器的文本數據。瀏覽器在一定時間內保存它,在下一次請求服務器時會附帶這個cookie數據。cookie通常被用來進行用戶的會話管理(登錄狀態),保存用戶的個性化信息(比如語言設置,視頻上次播放的位置,網站主題選項等)以及記錄和手機用戶瀏覽數據以來分析用戶行為等。
在Flask中,如果想要在響應中加一個cookie,可以用Response類型的set_cookie()方法。要用這個方法,需要先使用make_response()方法手動生成一個響應對象,傳入響應主體作為參數。這個響應對象默認實例化內置的Response類
Response類型的常用屬性和方法:
set_cookie()方法支持多個參數來設置cookie的選項:
在response中設置cookie
set_cookie視圖用來設置cookie,它會把URL中的name變量的值設置到名為name1的cookie里
set_cookie視圖會在生成的的報文首部中創建一個Set_Cookie字段,
即”Set-Cookie: name1=xiaxiaoxu; Path=/”
from flask import Flask,make_response,redirect
@app.route('/hello/<name>')
def hello(name):
return '<h1>Hello, %s !</hi>' %name
@app.route('/set/<name>')
def set_cookie(name):
#url_for()方法的第一個參數是視圖函數名,第二個參數是該函數需要的參數
response = make_response(redirect(url_for('hello', name = 'xiaxiaoxu')))
#把url中name變量的值添加到cookie中name屬性中
response.set_cookie('name1',name)
return response
if __name__ == '__main__':
app.run(debug = True)
結果:
在瀏覽器的cookie中,可以看到多了一個名為name1的cookie,它的值是”xiaxiaoxu”。
這個cookie會在關閉瀏覽器時過期。
第一個請求的響應中設置了cookie
當瀏覽器保存了服務器端設置的cookie后,當瀏覽器再次發送到該服務器的請求會自動攜帶設置的cookie信息,cookie的內容存儲在請求首部的cookie字段中
第二個請求的請求中有攜帶cookie信息
Flask中,cookie可以通過請求對象的cookies屬性讀取。
我們修改一下hello視圖函數,如果沒有從查詢參數中獲取到name的值,就從cookies中尋找
@app.route('/')
@app.route('/hello')
def hello():
name = request.args.get('name')
if name is None:
name = request.cookies.get('name','Human')#從cookie中獲取name值
return '<h1>Hello, %s</hi>' % name
@app.route('/set/<name>')
def set_cookie(name):
response = make_response(redirect(url_for('hello',name = name)))
response.set_cookie('name1',name)
return response
if __name__ == '__main__':
app.run(debug = True)
結果:
瀏覽器中 輸入…/set/xiaxiaoxu
重定向后:地址是…/hello?name=xiaxiaoxu
這里的?后的參數是通過redirect(url_for('hello',name = name)這句生成的,因為hello()視圖函數沒有提供參數,所以這里直接把name=name這句加到了url后面
瀏覽器中輸入127.0.0.1:5000/
此時name的值為空,那么name從cookies里面取值,取到一個默認值Human
session:即安全的cookie,就是把cookie數據進行了加密
session的作用
session在web程序中發揮很大的作用,其中最重要的功能是存儲用戶的認證信息。我們先來看看基於瀏覽器的用戶認證是如何實現的。當我們使用瀏覽器登錄某個社交網站時,會在登錄表單中填寫用戶名和密碼,單擊登錄按鈕后,這會向服務器發送一個包含認證數據的請求。服務器接收請求后會查找對應賬戶,然后驗證密碼是否匹配,
如果匹配,就在返回的響應中設置一個cookie,比如,”login_user: sam”
響應被瀏覽器接收后,cookie會被保存在瀏覽器中。當用戶再次想這個服務器發送請求時,根據請求附帶的cookie字段中的內容,服務器上的程序就可以判斷用戶的認證狀態,並識別出用戶。
Flask 用session對象用來加密cookie數據
但是會帶來用一個問題,在瀏覽器中手動添加和修改cookie是很容易的事,僅僅通過瀏覽器插件就可以實現。所以,如果直接把認證信息以明文的方式存儲在cookie里,那么惡意用戶就可以通過偽造cookie的內容來獲得對網站的權限,冒用別人的賬戶。為了避免這個問題,我們需要對敏感的cookie內容進行加密。方便的是,Flask提供了session對象用來將cookie數據加密儲存。
在編程中,session指用戶會話(user session),又稱為會話,即服務器和客戶端/瀏覽器之間或桌面程序和用戶之間建立的交互活動。在Flask中,session對象用來加密Cookie。默認情況下,它會把數據存儲在一個名為session的cookie里。
設置程序密鑰
session通過密鑰對數據進行簽名以加密數據,因此,我們得先設置一個密鑰。這里的秘鑰就是一個具有復雜度和隨機性的字符串。
程序的秘鑰可以通過Flask.secret_key屬性或配置變量SECRET_KEY設置。
如:
app.secret_key = ‘secret string’
更安全的做法是把密鑰寫進系統環境變量(在命令行是用export或set命令),或者保存在.env文件中:
SECRET_KEY = secret string
然后在程序中用os模塊提供的getenv()方法獲取:
import os
…
app.secret_key = os.getenv(‘SECRET_KEY’, ‘secret string’)
在getenv()方法中的第二個參數是作為沒有獲取到環境變量時使用的默認值。
模擬用戶認證:
使用session模擬用戶的認證功能,下面代碼是是登錄的login視圖
session對象可以像字典一樣操作,我們向session中添加一個名為logged_in的cookie,值為True,表示用戶已認證。
當我們使用session對象添加cookie時,數據會使用程序的密鑰對其進行簽名,加密后的數據存儲在一塊名為session的cookie里
from flask import redirect, session, url_for
import os
app.secret_key = os.getenv('SECRET_KEY','secret string')
@app.route('/login')
def login():
session['logged_in'] = True #寫入session
return redirect(url_for('hello'))
@app.route('/')
@app.route('/hello')
def hello():
name = request.args.get('name')
if name is None:
name = request.cookies.get('name','Human')#從cookie中獲取name值
return '<h1>Hello, %s</hi>' % name
if __name__ == '__main__':
app.run(debug = True)
結果:
查看cookie頁面,可以看到session的值是加密處理cookie后的值。使用session對象存儲的cookie,用戶可以看到其加密后的值,但不能修改。因為session中的內容使用密鑰進行簽名加密,一旦數據被修改,簽名的值也會變化,這樣在讀取時,就會驗證失敗,對應的session值也會隨之失效。
所以,除非用戶知道密鑰,否則無法對session cookie的值進行修改
第一次請求,在響應中設置了session
重定向后的請求數據:第二次請求時,會在請求頭中加入session的信息
支持用戶登錄后,就可以根據用戶的認證狀態分別顯示不同的內容,在login視圖的最后,重定向到hello視圖,我們修改一下hello視圖,判斷一下session中的認證狀態,區分開處理
session中的數據可以像字典一樣通過鍵讀取,在這里只判斷一下session中是否包含loggedd_in鍵,如果有則表示用戶已經登錄,在響應中加一行[Authenticated],否則顯示[Not authenticated]
如果訪問127.0.0.1:5000/login,就會登入當前用戶,session中就會存儲logged_in的信息,重定向到127.0.01:5000/hello后你會發現加載后的頁面顯示一行”[Authenticated]”,表示用戶已經通過認證
from flask import redirect, session, url_for
import os
app.secret_key = os.getenv('SECRET_KEY','secret string')
@app.route('/login')
def login():
session['logged_in'] = True #寫入session
return redirect(url_for('hello'))
@app.route('/')
@app.route('/hello')
def hello():
name = request.args.get('name')
if name is None:
name = request.cookies.get('name','xiaxiaoxu')#從cookie中獲取name值
response = '<h1>Hello, %s</h1>' % name
#根據用戶認證狀態返回不同內容
if 'logged_in' in session:
response += '<p>[Authenticated]</p>'
else:
response += '[Not Authenticated]'
return response
if __name__ == '__main__':
app.run(debug = True)
結果:
模擬管理后台admin的視圖
from flask import session, abort
import os
app.secret_key = os.getenv('SECRET_KEY','secret string')
@app.route('/login')
def login():
session['logged_in'] = True #寫入session
return redirect(url_for('hello'))
@app.route('/admin')
def admin():
if 'logged_in' not in session:
abort(403)
return 'welcom to admin page'
if __name__ == '__main__':
app.run(debug = True)
結果:
沒有存儲登錄cookie的情況下直接訪問admin
會提示403
登錄127.0.0.1:5000/login,寫入session后,再次訪問admin
此時session里已經存儲了logged_in cookie信息,所以可以訪問
模擬用戶退出登錄logout視圖
已登錄用戶的退出,對應的操作就是它代表用戶認證的logged_in cookie刪除,這樣用戶的登錄狀態就失效了,通過session對象的pop方法實現:
@app.route('/logout')
def logout():
if 'logged_in' in session:
session.pop('logged_in')
return redirect(url_for('hello'))
結果:
在登錄狀態下,直接訪問127.0.0.1:5000/logout
在logout視圖中,清楚了session的logged_in信息,然后重定向到hello視圖
重定向后的/hello頁面的認證狀態信息會變為[Not authenticatedd]
默認情況下,session cookie會在用戶關閉瀏覽器時刪除。通過將session.permanent屬性設為True可以將session的有效期延長為Flask.permanent_session_lifetime屬性值對應的datetime.timedelta對象,也可通過配置變量PERMANENT_SESSION_LIFETIME設置,默認為31天
盡管session對象會對cookie進行簽名並加密,但這種方式僅能夠確保session的內容不會被篡改,加密后的數據借助工具仍然可以輕易讀取(即使不知道秘鑰)。因此,絕對不能再session中存儲敏感信息,比如用戶密碼。