编写login的业务逻辑
首先验证登陆需要确认账号和密码是否正确,那么在前面密码是加密过后保存在数据库中的,所以再读取之前需要解密,在进行对比,这一系列操作可以使用flask中自带的check_password_hash
来完成
# forms/sql_user.py
from werkzeug.security import generate_password_hash, check_password_hash
·····
#raw为传入的对比数据
def check_password(self, raw):
return check_password_hash(self._password, raw)
验证成功后需要设置用户的登陆凭证,即cookie
,设置cookie
可以使用flask
的插件flask-login
来完成,使用pip install flask-login
来安装插件,利用下面的login_user
来设置cookie
首先,先导入flask-login
,并且注册到app
上
#app/__init__.py
from flask import Flask
from app.models.sql_book import db
from flask_login import LoginManager
#实例化
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
app.config.from_object('app.secure')
app.config.from_object('app.setting')
register_blueprint(app)
db.init_app(app)
db.create_all(app=app)
#将login_manager注册到app上
login_manager.init_app(app)
return app
def register_blueprint(app):
from app.web import web
app.register_blueprint(web)
然后使用验证
·····
from flask_login import login_user
·····
@web.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
# 从数据库中查询email这条记录
user = User.query.filter_by(email=form.email.data).first()
if user and user.check_password(form.password.data):
#设置cookie
login_user(user)
else:
flash('账号不存在或密码错误')
return render_template('auth/login.html', form=form)
访问127.0.0.1:5000/login/
,登陆已经注册过的账户,在控制台中可以查看用户的cookie
信息
访问权限控制
首先要安装flask
的扩展flask-login
使用pip install flask_login
来安装,然后实例化并注册到app
上
#app/__init__.py
from flask import Flask
from app.models.sql_book import db
from flask_login import LoginManager
# 实例化
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
app.config.from_object('app.secure')
app.config.from_object('app.setting')
register_blueprint(app)
db.init_app(app)
db.create_all(app=app)
#注册到app上
login_manager.init_app(app)
return app
def register_blueprint(app):
from app.web import web
app.register_blueprint(web)
你用来表示用户的类需要实现一些方法和属性
is_authenticated
当用户通过验证时,也即提供有效证明时返回 True 。(只有通过验证的用户会满足 login_required 的条件。)
is_active
如果这是一个活动用户且通过验证,账户也已激活,未被停用,也不符合任何你 的应用拒绝一个账号的条件,返回 True 。不活动的账号可能不会登入(当然, 是在没被强制的情况下)。
is_anonymous
如果是一个匿名用户,返回 True 。(真实用户应返回 False 。)
get_id()
返回一个能唯一识别用户的,并能用于从 user_loader 回调中加载用户的 unicode 。注意着 必须 是一个 unicode —— 如果 ID 原本是 一个 int 或其它类型,你需要把它转换为 unicode 。
但是可以通过UserMixin
这个类来继承,并且你必须提供一个 user_loader 回调。这个回调用于从会话中存储的用户 ID 重新加载用户对象。它应该接受一个用户的 unicode ID 作为参数,并且返回相应的用户对象。
这个方法可以卸载sql_user.py
下,但不是User
的类方法
from sqlalchemy import Column, Integer, Float, String, Boolean
from werkzeug.security import generate_password_hash, check_password_hash
from app.models.base import Base, db
from app import login_manager
from flask_login import UserMixin
class User(Base, UserMixin):
id = Column(Integer, primary_key=True)
_password = Column('password', String(128), nullable=False)
nickname = Column(String(24), nullable=False)
phone_number = Column(String(18), unique=True)
email = Column(String(50), unique=True, nullable=False)
confirmed = Column(Boolean, default=False)
beans = Column(Float, default=0)
send_counter = Column(Integer, default=0)
receive_counter = Column(Integer, default=0)
wx_open_id = Column(String(50))
wx_name = Column(String(32))
@property
def password(self):
return self._password
@password.setter
def password(self, raw):
self._password = generate_password_hash(raw)
def check_password(self, raw):
return check_password_hash(self._password, raw)
@login_manager.user_loader
def get_user(uid):
return User.query.get(int(uid))
然后只需要在需要登陆才可以访问的页面前加上装饰器即可实现功能,例如在gitf.py
下的一个视图函数
from . import web
#导入login_required模块
from flask_login import login_required
@web.route('/my/gifts')
@login_required
def my_gifts():
return 'hello'
##未登录重定向和登陆后重定向
未登录当然不可能返回一个hello
,一般网站的处理是重定向到登陆界面,并且登陆后再次跳转到未登录前的界面。
实现跳转登陆界面
# app/__init__.py
from flask import Flask
from app.models.sql_book import db
from flask_login import LoginManager
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
app.config.from_object('app.secure')
app.config.from_object('app.setting')
register_blueprint(app)
db.init_app(app)
db.create_all(app=app)
login_manager.init_app(app)
login_manager.login_view = 'web.login'
login_manager.login_message = '请先登录'
return app
def register_blueprint(app):
from app.web import web
app.register_blueprint(web)
实现跳转到登陆前的页面
@web.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(email=form.email.data).first()
if user and user.check_password(form.password.data):
login_user(user)
# 观察重定向登陆页面的url,跳转前的页面被加载了next参数上,所以只需要拿到这个参数即可
next_page = request.args.get('next')
# 判断是否为空,如果为空跳转到首页
# 这里的首页在 web/main.py下,需要返回一个response不然就会报错。
if not next_page:
next_page = url_for('web.index')
return redirect(next_page)
else:
flash('账号不存在或密码错误')
return render_template('auth/login.html', form=form)
重定向攻击
上面的代码存在安全隐患,因为next
的参数可控,存在重定向攻击的风险,所以我们需要判断这个next
参数
这里给出简单判断,也可以使用白名单来校验
#将next_page的判断条件修改
if not next_page or not next_page.startswith('/'):