前言:本文是学习网易微专业的《python全栈工程师 - Flask高级建站》课程的笔记,欢迎学习交流。同时感谢老师们的精彩传授!
http协议中并没有对用户在多次访问之间指定身份认证规则,所以即使同一个人在同一个浏览器窗口访问同一个网站,服务器也无法知道两次访问是否是同一个人,这样就会带来身份识别的问题。
现代浏览器都提供一种cookie
技术,当用户访问网站的时候,可以在浏览器端记录一小段内容。每次访问的时候,在访问数据头部都会带上这一段cookie
提交给服务器,这样服务器可以根据cookie
来判定用户身份
cookie
对象既可以在浏览器端访问,也可以在服务器端访问。
浏览器端读取:document.cookie
服务器端写入:response.set_cookie("key", value)
服务器端读取:request.cookies.get('key')
参数 | 说明 |
---|---|
key | cookie 键名 |
value | 键名对应的值 |
expires | 过期时间,默认关闭浏览器即失效 |
path | 限制cookie 有效路径,默认在全站有效 |
domain | 设置cookie 的可用域名 |
secure | 设为true ,只有https 才可以使用 |
httponly | 无法通过js 获取cookie |
cookie
对象由于可以在浏览器端通过js
修改与查看,存在安全隐患,所以不能直接通过cookie
来完成用户识别
session
会话机制
1.直接将cookie
内容加密,如果加密方式暴露,两样有安全问题
2.将敏感信息存在服务器端,在浏览器存储一个加密的会话id
,该id
如果丢失,同样会造成安全问题。
内置session
对象是通过将cookie
内容加密传输,在服务器端,session
对象使用一个密钥加密,所以使用session
必须先设置密钥:
app.secret_key = "......"
通过session
对象设置session
键值对。比如:session['username']="luxp"
实操:
Step1
:引入flask
的session
模块
from flask import session
Step2
:设置加密密钥
app.secret_key = "123456"
Step3
:添加测试session
用的视图函数
@app.route('/test_session')
def test_session():
try:
session['number'] += 1
except:
session['number'] = 0
return "number=" + str(session['number'])
用户登录的时候,通常会使用用户名+密码的方式登录,这就要求用户名必须唯一,否则无法识别用户身份,因此需要检查用户名是否唯一。
修改views/users.py
,在register()
后添加以下代码:
# 验证用户名是否重复
def validate_username(username):
return User.query.filter_by(username=username).first()
同时,修改register()
视图函数为以下内容:
@user_app.route("/register", methods=['GET', 'POST'])
def register():
message = None
if request.method == 'POST':
if validate_username(request.form['username']):
return render_template("/user/register.html", message="用户名重复")
realname = request.form['name']
username =request.form['username']
password = request.form['password']
sex = request.form['sex']
mylike = '|'.join(request.form.getlist('like'))
city = request.form['city']
intro = request.form['intro']
user = User(
realname = realname,
username = username,
password = password,
sex = sex,
mylike = mylike,
city = city,
intro = intro
)
try:
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
message = "注册失败:" + str(e)
return render_template("user/register.html", message=message)
现在密码都是直接明文存贮在数据库中,一旦数据库被攻破,用户的密码主举发生泄漏,成为非常严重的安全问题。因此,用户的密码都是存储加密之后的密码
。
加密有很多算法,比如md5, sha1
,在flask
中提供了专用的密码生成方法,采用sha256
算法,加密结果是不可逆。
from werkzeug.security import generate_password_hash
class User(...):
...
def set_password(self, password):
self.password = generate_password_hash(password)
实操:
Step1
:在models.py
中引入加密算法库
from werkzeug.security import generate_password_hash
Step2
:修改models.py
文件里的User
模型对象,添加以下代码:
def hash_password(self, password):
self.password = generate_password_hash(password)
Step3
:替换views/users.py
文件中register()
视图函数为以下内容:
@user_app.route("/register", methods=['get','post'])
def register():
message = None
if request.method == 'POST':
if validate_username(request.form['username']):
return render_template("/user/register.html", message="用户名重复")
realname = request.form['name']
username = request.form['username']
password = request.form['password']
sex = request.form['sex']
mylike = '|'.join(request.form.getlist('like'))
city = request.form['city']
intro = request.form['intro']
user = User(
realname = realname,
username = username,
# password = password 减少这一行 -
sex = sex,
mylike = mylike,
city = city,
intro = intro
)
# 密码加密 新增这一行 +
user.hash_password(password)
try:
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
message = "注册失败:" + str(e)
return render_template("user/register.html", message=message)
用户登录的时候,输入的是加密前的密码字符串,不能直接与数据库存储的加密结果比较。
必须将用户的输入用同样的加密之后再与数据库中的比较。
from werkeug.security import check_password_hash
class User(...):
...
def validate_password(self, password):
return check_password_hash(self.password, password)
实操:
Step1
:在models.py
中引入验证加密算法
from werkzeug.security import check_password_hash
Step2
:替换models.py
中的User
模型对象为以下代码:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
password = db.Column(db.String)
realname = db.Column(db.String)
sex = db.Column(db.Integer)
mylike = db.Column(db.String)
city = db.Column(db.String)
intro = db.Column(db.String)
def hash_password(self, password):
self.password = generate_password_hash(password)
# 新增下面的密码验证方法
def validate_password(self, password):
return check_password_hash(self.password, password)
Step3
:修改app.py
文件中的login()
登录函数,以下代码:
@app.route('/login', methods=['get', 'post'])
def login():
message = None
if request.method == "POST":
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.validate_password(password):
session['user'] = user.username
# 登录成功返回首页
return redirect(url_for("index"))
else:
message = "用户名与密码不匹配"
return render_template("login.html", message = message)
def login():
if 已经登录:
return redirect("/")
else:
user = User.query.filter_by(username=username).first()
if user:
if user.validate_password(password):
session['user'] = user.username
登录之前:
1.显示登录、注册按钮
2.不显示文章管理、分类管理、用户管理等导航链接
登录之后:
1.显示用户名
2.显示文章管理、分类管理、用户管理
实操:
Step1
:替换app.py
文件中的account()
函数,用以下代码:
@app.context_processor
def account():
username = session.get('user')
return {"username": username}
Step2
:修改templates/base.html
的导航栏部分:
注:竖的三个省略号,表示省略显示其他代码,这里只写需要更改的代码
.
.
.
<ul class="nav navbar-nav" >
<li class="active">首页a>li>
{% for cate in cates %}
<li>
<a href="{{ url_for('article_app.getArticleList', cate_id=cate.cate_id, page=1) }}">
{{ cate.cate_name }}
a>
li>
{% endfor %}
{% if username %}
<li>文章管理a>li>
<li>用户管理a>li>
<li>分类管理a>li>
{% endif %}
ul>
<ul class="nav navbar-nav pull-right">
{% if not username %}
<li>登录a>li>
<li>注册a>li>
{% else %}
<li><a href="/usercenter">{{ username }}a>li>
<li><a href="">退出a>li>
{% endif %}
ul>
.
.
.
为了防止未登录用户访问需要登录的页面url
,需要对这些页面进行保护。以下视图只有登录用户才能访问:
1.文章发布、文章删除、文章修改、文章管理列表
2.用户管理列表、用户删除、用户修改
3.分类管理、分类修改、分类删除
对于需要用户登录后才能进行的操作,必须进行身份验证。可以在需要登录的视图函数前使用登录装饰器。
def login_required(func):
@wraps(func)
def decorator_nest(*args, **kwargs):
if not "user" in session:
return redirect(url_for("login"))
else:
return func(*args, **kwargs)
return decorator_nest
实操:
step1
:替换libs.py
文件,以下列代码:
from flask_sqlalchemy import SQLAlchemy
from flask import session, redirect, url_for
from functools import wraps
# 创建数据库对象
# 这里不用传入app实例对象,因为这里尚未创建app实例
db=SQLAlchemy()
def login_required(func):
@wraps(func)
def decorator_nest(*args, **kwargs):
if not "user" in session:
return redirect(url_for("login"))
else:
print(func)
return func(*args, **kwargs)
return decorator_nest
Step2
:在需要保护的视图函数前,添加@login_required
装饰器。
比如:views/users.py
文件中的userList()
注:竖的三个省略号,表示省略显示其他代码,这里只写需要更改的代码
.
.
.
from libs import db, login_required
.
.
.
@login_required
def userList():
.
.
.
views/articles.py
文件中的post()
函数、edit()
函数、delete()
函数
.
.
.
from libs import db, login_required
.
.
.
@login_required
def post():
.
.
.
@login_required
def delete(article_id):
.
.
.
@login_required
def edit(article_id):
.
.
.
默认情况下,用户关闭浏览器后,身份就会丢失,也可以提供一个退出功能,让用户可以自主选择什么时候退出。
def login_out():
if session.get("user"):
session.pop("user")
return redirect(url_for("index"))
实操:
Step1
:在app.py
文件中 ,login()
函数的后面添加以下代码:
@app.route('/logout')
def logout():
if session.get("user"):
session.pop("user")
return redirect(url_for("index"))
Step2
:修改templates/base.html
文件中的退出
的路由:
.
.
.
<ul class="nav navbar-nav pull-right">
{% if not username %}
<li>登录a>li>
<li>注册a>li>
{% else %}
<li><a href="/usercenter">{{ username }}a>li>
<li><a href="{{ url_for('logout') }}">退出a>li>
{% endif %}
ul>
.
.
.
cookie
cookie
用于浏览器跟服务器之间传递数据,这样在多次访问的时候,可以维持数据的传输,但是cookie
不够安全,因为在浏览器端可以直接查看cookie
的内容。如果存储权限敏感的信息,很容易被泄漏。session
flask
的session
对cookie
进行了加密,加密之后,用户没办法直接从浏览器看到数据。在身份的登录里,我们使用session
来维护用户的身份,这样可以在每个视图里都可以得到session
的用户身份。