关于cookie的报文首部相关属性熟悉后,下面就是实际应用。
使用cookie实现用户登录验证(初步):
思路(一):显示登录页面,输入用户和密码,后端验证,如果验证通过,则登录成功,响应报文中将用户、密码写入cookie,访问其他页面时,通过cookie获取用户信息,能够获取,则直接访问页面,不能获取,则跳转至登录页面。
细节再加工:第一次登录成功后,在cookie的有效时间内,再次访问(登录页面),不显示登录页面直接跳转至主页面(登录用户还有效)
代码:cookiedemo.py
from bottle import template, Bottle,request,response,redirect
import os,time,datetime
import bottle
bottle.TEMPLATE_PATH.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")))
root = Bottle()
@root.route('/login/',method=["GET","POST"])
# 装饰器,定义了URL,即/hello/这个url由index这个函数来处理,就是路由系统
def login():
message=""
if request.method == "GET":
# GET方法请求时,也判断是否已登录,即cookie是否保存用户名和密码
username=request.get_cookie("username")
password=request.get_cookie("passwd")
if username == "root" and password == "222222":
# 如果cookie存在对用的用户和密码,则直接跳转到index主页面
redirect("/index/")
else: # 否则,打开登录页面
return template("login.html")
else: # 对于POST方法,是登录过程,要获取用户名和密码
username = request.POST.get("user")
password = request.POST.get("pwd")
print(username,password)
if not username:
message = "用户名不能为空"
print(message)
return template("login.html",msg=message)
else:
print("查询用户名是否存在,密码是否匹配:")
print("select username,password from user where username=username and password=password")
if username == "root" and password == "222222":
print("用户存在,登录成功,写入cookie")
response.set_cookie("username","root",max_age=50,path="/")
response.set_cookie("passwd","222222",max_age=50,path="/")
redirect("/index/")
else:
message = "用户名或密码错误!"
print(message)
return template("login.html",msg=message)
@root.route('/index/')
def index():
username=request.get_cookie('username')
passward=request.get_cookie('passwd')
if username != "root" or passward != "222222":
redirect('/login/')
else:
return template("index.html",username=username)
@root.route("/content1.html")
def cont1():
username=request.get_cookie('username')
passward=request.get_cookie('passwd')
if username != "root" or passward != "222222":
redirect('/login/')
else:
return template("content1.html",username=username)
@root.route("/content2.html")
def cont2():
username=request.get_cookie('username')
passward=request.get_cookie('passwd')
if username != "root" or passward != "222222":
redirect('/login/')
else:
return template("content2.html",username=username)
root.run(host='localhost', port=8080)
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Bottle登录</h1>
<form action="/login/" method="post">
<input type="text" name="user" placeholder="用户名">
<input type="password" name="pwd" placeholder="密码">
<input type="submit" value="提交">
<span style="color: red">{{get('msg','')}}</span>
</form>
</body>
</html>
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>业务主页面</h1>
<hr/>
<span style="color: green">登录用户:{{get('username','')}}</span><br>
<a href="/content1.html">业务一</a><br>
<a href="/content2.html">业务二</a>
</body>
</html>
<!-- content1.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>业务一主页</h1>----{{get("username","")}}
</body>
</html>
上面的实现,存在的问题有:
- 在cookie有效期内,每次访问都发送用户名和密码,并且是明码,安全性差,被截获的可能性很大;
- 判断是否登录的代码重复出现;
解决发送明码用户名和密码的问题,可以使用加密的方法,将用户名和密码改为加密字符串保存到cookie中,使用这个串来验证用户;代码重复的问题,可以将相关代码抽取,形成单独的函数,做成装饰器。
解决安全性,使用md5加密:
from bottle import template, Bottle,request,response,redirect
import os,time,datetime
import bottle
from hashlib import md5
bottle.TEMPLATE_PATH.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")))
root = Bottle()
userdb={"root":"222222","admin":"333333"} # 模拟用户数据库
md5db={}
# 数据示例如下:
# {"md5":{"username":"",
# "is_login":True,
# "expiredate":""}} # 登录后保存用户信息
def myauth(username,passwd):
# 模拟用户验证,存在用户且密码正确,返回True
if userdb.get(username) == passwd:
return True
else:
return False
def mymd5(md5val=None):
# 产生MD5值,操作md5db,用户登录成功后,保存相关信息
#
nowtime = datetime.datetime.utcfromtimestamp(time.time())
if md5val in md5db.keys():
tem = md5db[md5val]
if tem['is_login'] == True and tem['expiredate'] > nowtime:
md5val_new = md5(((tem['username']+str(nowtime)).encode('utf-8'))).hexdigest()
md5db[md5val]['expiredate']= datetime.datetime.utcfromtimestamp(time.time() + 60)
md5db[md5val_new] = md5db.pop(md5val)
return [md5val_new,md5db[md5val_new]]
else:
return None
@root.route('/login/',method=["GET","POST"])
# 装饰器,定义了URL,即/hello/这个url由index这个函数来处理,就是路由系统
def login():
message=""
if request.method == "GET":
md5val=request.get_cookie('hashval')
tem = mymd5(md5val=md5val)
if tem:
response.set_cookie("hashval",tem[0])
return redirect("/index/")
else: # 否则,打开登录页面
return template("login.html")
else: # 对于POST方法,是登录过程,要获取用户名和密码
username = request.POST.get("user")
password = request.POST.get("pwd")
if not username:
message = "用户名不能为空"
print(message)
return template("login.html",msg=message)
else:
print("查询用户名是否存在,密码是否匹配:")
if myauth(username,password):
print("用户存在,登录成功,写入cookie")
nowtime = datetime.datetime.utcfromtimestamp(time.time() + 60)
md5val_new = md5((username + str(nowtime)).encode('utf-8')).hexdigest()
md5db[md5val_new]={'username':username,'is_login':True,'expiredate':nowtime}
response.set_cookie('hashval',md5val_new,expires=nowtime,path='/')
return redirect("/index/")
else:
message = "用户名或密码错误!"
print(message)
return template("login.html",msg=message)
@root.route('/index/')
def index():
md5val = request.get_cookie("hashval")
tem = mymd5(md5val)
if tem:
md5val_new = tem[0]
expiredate = tem[1]['expiredate']
response.set_cookie('hashval',md5val_new,expires=expiredate,path='/')
return template("index.html",username=tem[1]['username'])
else:
return redirect("/login/")
@root.route("/content1.html")
def cont1():
md5val = request.get_cookie("hashval")
tem = mymd5(md5val)
if tem:
md5val_new = tem[0]
expiredate = tem[1]['expiredate']
response.set_cookie('hashval',md5val_new,expires=expiredate,path='/')
return template("content1.html", username=tem[1]['username'])
else:
return redirect("/login/")
@root.route("/content2.html")
def cont2():
md5val = request.get_cookie("hashval")
tem = mymd5(md5val)
if tem:
md5val_new = tem[0]
expiredate = tem[1]['expiredate']
response.set_cookie('hashval',md5val_new,expires=expiredate,path='/')
return template("content2.html", username=tem[1]['username'])
else:
return redirect("/login/")
root.run(host='localhost', port=8080)
这样,cookie传递的是用户md5后的散列值,并且每次请求都不一样,一定程度上解决了用户、密码安全性问题。
解决重复问题,使用装饰器:
from bottle import template, Bottle,request,response,redirect
import os,time,datetime
import bottle
from hashlib import md5
bottle.TEMPLATE_PATH.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")))
root = Bottle()
userdb={"root":"222222","admin":"333333"}
md5db={}
# 键值对示例如下:
# {"md5":{"username":"",
# "is_login":True,
# "expiredate":""}}
def myauth(username,passwd):
# 模拟用户验证,存在用户且密码正确,返回True
if userdb.get(username) == passwd:
return True
else:
return False
def mymd5(md5val=None):
# 产生MD5值,操作md5db,用户登录成功后,保存相关信息
#
nowtime = datetime.datetime.utcfromtimestamp(time.time())
if md5val in md5db.keys():
tem = md5db[md5val]
if tem['is_login'] == True and tem['expiredate'] > nowtime:
md5val_new = md5(((tem['username']+str(nowtime)).encode('utf-8'))).hexdigest()
md5db[md5val]['expiredate']= datetime.datetime.utcfromtimestamp(time.time() + 60)
md5db[md5val_new] = md5db.pop(md5val)
return [md5val_new,md5db[md5val_new]]
else:
return None
tem = None
def logindecorator(f):
print("装饰器")
def inner(*args,**kwargs):
global tem
md5val = request.get_cookie('hashval')
tem = mymd5(md5val)
print("执行函数:",f.__name__)
print("===>", tem)
if tem:
response.set_cookie("hashval",tem[0],expires=tem[1]['expiredate'],path="/")
return f() # 注意,这里必须使用return
else:
redirect("/login/")
return inner
@root.route('/login/',method=["GET","POST"])
# 装饰器,定义了URL,即/hello/这个url由index这个函数来处理,就是路由系统
def login():
message=""
if request.method == "GET":
md5val=request.get_cookie('hashval')
tem = mymd5(md5val=md5val)
if tem:
response.set_cookie("hashval",tem[0])
return redirect("/index/")
else: # 否则,打开登录页面
return template("login.html")
else: # 对于POST方法,是登录过程,要获取用户名和密码
username = request.POST.get("user")
password = request.POST.get("pwd")
if not username:
message = "用户名不能为空"
print(message)
return template("login.html",msg=message)
else:
print("查询用户名是否存在,密码是否匹配:")
if myauth(username,password):
print("用户存在,登录成功,写入cookie")
nowtime = datetime.datetime.utcfromtimestamp(time.time() + 60)
md5val_new = md5((username + str(nowtime)).encode('utf-8')).hexdigest()
md5db[md5val_new]={'username':username,'is_login':True,'expiredate':nowtime}
response.set_cookie('hashval',md5val_new,expires=nowtime,path='/')
return redirect("/index/")
else:
message = "用户名或密码错误!"
print(message)
return template("login.html",msg=message)
@root.route('/index/')
@logindecorator
def index():
return template("index.html", username=tem[1]['username'])
@root.route("/content1.html")
@logindecorator
def cont1():
return template("content1.html", username=tem[1]['username'])
@root.route("/content2.html")
@logindecorator
def cont2():
return template("content2.html", username=tem[1]['username'])
root.run(host='localhost', port=8080)
如此,重复的问题也解决了。
关于session:
Cookie是一种记录客户状态的机制,如前面的例子,用户信息保存在客户端,而Session是另一种记录客户状态的机制,不同于Cookie的是,Session将用户信息保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。服务器要查询客户档案,也需要一个标识,这个标识是服务器在建立session是生成的一个字符串(加密的),然后保存到客户端的cookie。所以,session也需要使用cookie。
Session 代表着服务器和客户端一次会话的过程。Session 对象存储:特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。
如下示例:
from flask import Flask,render_template,redirect,request,make_response,session,url_for
app = Flask(__name__)
app.config.update(SECRET_KEY=b'qwe123!@#1qazxsw23edcvfr')
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/login/',methods=['GET','POST'])
def login():
if request.method == 'POST':
session['user'] = request.form.get('user') # 设置session内容
return redirect('/index/')
else:
return render_template('login.html')
@app.route('/index/')
def index():
print(session)
return render_template('index.html')
if __name__ == '__main__':
app.run()
在后台打印的结果:<SecureCookieSession {'user': 'root'}>
通过抓包,可以发现,所谓的session就是在cookie中保存了一个key为session,value是加密字符串的东西,而在服务器端,保存了对应的用户信息,就是SecureCookieSession对象,其中保存了user:root这个信息,通过session的值,就能找到这个对象及其相关信息。
所以,在前面cookie的例子中,实现的其实就是session的功能,md5db就相当于服务器端的session信息库,mymd5()就是服务器实现session的功能函数,保存到cookie中的hashval,就是这里的写入cookie的session。只不过这里session的实现更全面,考虑的东西更多。
测试session过期时间:
from flask import Flask,render_template,redirect,request,make_response,session,url_for
from datetime import timedelta
app = Flask(__name__)
app.config.update(SECRET_KEY=b'qwe123!@#1qazxsw23edcvfr')
userdb = {"root":"1","admin":"2"}
def myauth(username,password):
if userdb.get(username) == password:
return True
else:
return False
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/login/',methods=['GET','POST'])
def login():
if request.method == 'POST':
username = request.form.get('user')
password = request.form.get('pwd')
if myauth(username,password):
session['username'] = username # 设置session内容
session['is_login'] = True
session.permanent = True
app.permanent_session_lifetime = timedelta(seconds=50)
return redirect('/index/')
else:
return render_template('login.html')
else:
return render_template('login.html')
@app.route('/index/')
def index():
print("获取的session内容:",session.get('is_login'))
return render_template('index.html')
if __name__ == '__main__':
app.run()
通过session.permanent=True和app.permanent_session_lifetime,可以设置session有效时间,这里设置为50秒,然后可以看一下cookie的设置及后台打印结果:
get访问login时:
响应报文没有写cookie,登录:
因为是redirect,进行了两次请求,POST方法的login,以及GET方法的index
login的cookie被写入session键及值,过期时间是当前时间+50秒
index请求头中携带了cookie,即login过程中的session键值对。
后台打印:
session没有过期,所以获取了session内容。
50秒后,重新刷新index,没有cookie被发送,后台打印
如果在有效期内,刷新login或index,则会重复生成新的session键值对,并写入cookie,过期时间也会重新生成,在当前时间加上50秒。这跟前面测试cookie时自己写的模拟程序mymd5()功能一样。
加上装饰器,对是否登录进行自动判定:
from flask import Flask,render_template,redirect,request,make_response,session,url_for
from datetime import timedelta
app = Flask(__name__)
app.config.update(SECRET_KEY=b'qwe123!@#1qazxsw23edcvfr')
userdb = {"root":"1","admin":"2"}
def myauth(username,password):
if userdb.get(username) == password:
return True
else:
return False
@app.route('/')
def hello_world():
return 'Hello World!'
def logindecorator(f):
def inner(*args,**kwargs):
if session.get('is_login'):
return f()
else:
return redirect('/login/')
return inner
@app.route('/login/',methods=['GET','POST'])
def login():
if request.method == 'POST':
username = request.form.get('user')
password = request.form.get('pwd')
if myauth(username,password):
session['username'] = username # 设置session内容
session['is_login'] = True
session.permanent = True
app.permanent_session_lifetime = timedelta(seconds=50)
return redirect('/index/')
else:
return render_template('login.html')
else:
if session.get('is_login'):
return redirect('/index/')
else:
return render_template('login.html')
@app.route('/index/')
@logindecorator
def index():
print("获取的session内容:",session.get('is_login'))
return render_template('index.html',username=session.get('username'))
if __name__ == '__main__':
app.run()
至此,使用cookie与session的原理与流程大体搞明白了。