編寫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('/'):