Python学习笔记:6.3.3 用户登录

前言:本文是学习网易微专业的《python全栈工程师 - Flask高级建站》课程的笔记,欢迎学习交流。同时感谢老师们的精彩传授!

一、课程目标

  • http协议与身份验证
  • cookie
  • session

二、详情解读

2.1.http协议存在的问题

http协议中并没有对用户在多次访问之间指定身份认证规则,所以即使同一个人在同一个浏览器窗口访问同一个网站,服务器也无法知道两次访问是否是同一个人,这样就会带来身份识别的问题。
Python学习笔记:6.3.3 用户登录_第1张图片

2.2.解决方案:cookie

现代浏览器都提供一种cookie技术,当用户访问网站的时候,可以在浏览器端记录一小段内容。每次访问的时候,在访问数据头部都会带上这一段cookie提交给服务器,这样服务器可以根据cookie来判定用户身份
Python学习笔记:6.3.3 用户登录_第2张图片

2.2.1.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
2.2.1.cookie的安全问题

cookie对象由于可以在浏览器端通过js修改与查看,存在安全隐患,所以不能直接通过cookie来完成用户识别

2.3.解决方案:session

session会话机制
1.直接将cookie内容加密,如果加密方式暴露,两样有安全问题
2.将敏感信息存在服务器端,在浏览器存储一个加密的会话id,该id如果丢失,同样会造成安全问题。
Python学习笔记:6.3.3 用户登录_第3张图片

2.3.1.内置session对象

内置session对象是通过将cookie内容加密传输,在服务器端,session对象使用一个密钥加密,所以使用session必须先设置密钥:

app.secret_key = "......"

通过session对象设置session键值对。比如:session['username']="luxp"

实操:
Step1:引入flasksession模块

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'])
2.4.3实现登录
2.4.1.用户凭借用户我与密码登录

用户登录的时候,通常会使用用户名+密码的方式登录,这就要求用户名必须唯一,否则无法识别用户身份,因此需要检查用户名是否唯一。

修改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)

2.4.2.改进用户密码机制

现在密码都是直接明文存贮在数据库中,一旦数据库被攻破,用户的密码主举发生泄漏,成为非常严重的安全问题。因此,用户的密码都是存储加密之后的密码

加密有很多算法,比如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)
2.4.3.密码验证方法

用户登录的时候,输入的是加密前的密码字符串,不能直接与数据库存储的加密结果比较。
必须将用户的输入用同样的加密之后再与数据库中的比较。

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)

2.4.4.登录过程
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
2.4.5.修改导航条显示

登录之前:
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>
.
.
.         
2.4.6.视图保护

为了防止未登录用户访问需要登录的页面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):
.
.
.
2.4.7.身份退出

默认情况下,用户关闭浏览器后,身份就会丢失,也可以提供一个退出功能,让用户可以自主选择什么时候退出。

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
    flasksessioncookie进行了加密,加密之后,用户没办法直接从浏览器看到数据。在身份的登录里,我们使用session来维护用户的身份,这样可以在每个视图里都可以得到session的用户身份。

你可能感兴趣的:(Python全栈工程师学习笔记)