上一篇:Vue 2.0 起步(4) 輕量級后端Flask用戶認證 - 微信公眾號RSS
上一篇采用了Flask-JWT token驗證。由于網(wǎng)站新的需要吆鹤,除了token固惯,現(xiàn)在要加上session管理柑晒,跟Flask-Security比較之后片酝,決定創(chuàng)建第4篇的第二版:Flask-Security同時管理token和session
如果你是克隆git里源碼的囚衔,注意工程目錄名是vue-tutorial/
,步驟:http://www.reibang.com/p/b3c76962e3d4
2019延伸閱讀推薦:帶你進入異步Django+Vue的世界 - Didi打車實戰(zhàn)(1)
技術準備
Flask-Security 如何Session CSRF和REST Token兼得
注意:python版本雕沿!源碼只能用Python2练湿,用Python3就會報錯:
AttributeError: 'NoneType' object has no attribute 'get_user'
。具體原因已查到审轮,是因為Flask_security初始化時肥哎,python3處理方式不同。源碼已更新疾渣。
步驟:
- 后端Flask app更新
- 前端vue.js ajax更新
1. 后端Flask app更新
app初始化時贤姆,引入Flask-Security,綁定數(shù)據(jù)庫里的User/Role
/app/_init_.py (隱藏了無關代碼)
from flask_security import Security, SQLAlchemyUserDatastore, current_user, \
UserMixin, RoleMixin, login_required, auth_token_required, http_auth_required
from flask_security.utils import encrypt_password
db = SQLAlchemy()
# models引用必須在 db/login_manager之后稳衬,不然會循環(huán)引用
from .models import User, Role
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
security.init_app(app, datastore=user_datastore)
return app
Flask-Security相關配置
/config.py (部分)
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess xxxx'
# 允許追蹤 login activities
SECURITY_TRACKABLE = True
# 密碼加密存儲
SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
SECURITY_PASSWORD_SALT = 'super-xxxx'
# 允許注冊 register霞捡,暫不需要郵件確認
SECURITY_REGISTERABLE = True
SECURITY_SEND_REGISTER_EMAIL = False
針對SECURITY_TRACKABLE
,我們的Model也需要增加字段
/app/models.py
roles_users
表:多對多關系的聯(lián)結表薄疚,一個User有多個Role碧信,一個Role有多個User
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))
Role
表:保存各個角色,如superuser, admin, author, editor, user
class Role(db.Model, RoleMixin):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
User
表:添加last_login_at, current_login_at, last_login_ip, current_login_ip, login_count
字段街夭,F(xiàn)lask-Security會自動追蹤用戶登錄
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
username = db.Column(db.String(64), unique=True, index=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
last_login_at = db.Column(db.DateTime())
current_login_at = db.Column(db.DateTime())
last_login_ip = db.Column(db.String(63))
current_login_ip = db.Column(db.String(63))
login_count = db.Column(db.Integer)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users'))#, lazy='dynamic'))
member_since = db.Column(db.DateTime(), default=datetime.utcnow)
mps = db.relationship('Subscription',
foreign_keys=[Subscription.subscriber_id],
backref=db.backref('subscriber', lazy='joined'),
lazy='dynamic',
cascade='all, delete-orphan')
注意:數(shù)據(jù)模型變動之后砰碴,需要用Flask-migration更新一下數(shù)據(jù)庫。
可以在管理命令行加入這些常用命令:
/manage.py
執(zhí)行python manage.py deploy
--> 初始化/升級數(shù)據(jù)模型
執(zhí)行python manage.py dropall
--> 刪除數(shù)據(jù)庫里所有表
執(zhí)行python manage.py initrole
--> 創(chuàng)建Roles和admin用戶板丽,賦于superuser角色
@manager.command
def deploy():
"""Run deployment tasks."""
from flask_migrate import init, migrate, upgrade
# migrate database to latest revision
try: init()
except: pass
migrate()
upgrade()
@manager.command
def dropall():
db.drop_all()
print "all tables dropped! remember to delete directory: migrations"
@manager.command
def initrole():
db.session.add(Role(name="superuser"))
db.session.add(Role(name="admin"))
db.session.add(Role(name="editor"))
db.session.add(Role(name="author"))
db.session.add(Role(name="user"))
pwd = os.getenv('FLASK_ADMIN_PWD') or raw_input("Pls input Flask admin pwd:")
db.session.add(User(email="admin", password=encrypt_password(pwd), active=True))
ins=roles_users.insert().values(user_id="1", role_id="1")
db.session.execute(ins)
db.session.commit()
print "Roles added!"
2. 前端vue.js ajax更新
Flask-Security一些默認地址:
注冊:/register
登錄:/login
注銷:/logout
比如呈枉,你手動打開 http://localhost:5000/login, Flask-Security會自動渲染一個登錄頁面埃碱,很簡陋猖辫,當然你可以自己寫頁面override它。
不過前端vue.js是用ajax訪問的砚殿,不在乎頁面漂亮與否啃憎。
根據(jù)Flask-Security 如何Session CSRF和REST Token兼得的步驟,來改寫vue
/src/components/Siderbar.vue
登錄函數(shù)login()似炎,注意Flask-Security是用email
作為唯一用戶名的辛萍,先用正則表達式獲得csrf_token,再獲取json格式的token
methods: {
login() {
if (!this.validation) return;
// get CSRF
var csrf_token = '';
this.$http.get('/login').then((response) => {
// 響應成功回調
var data = response.body;
// alert(JSON.stringify(response));
// <input id="csrf_token" name="csrf_token" type="hidden" value="1483433916##5b057abdef66da070c8385752b78f6c584f6ba41"><input
var csrf_token = '';
try {
csrf_token = data.match(/name="csrf_token" type="hidden" value="(.*?)">/)[1];
// alert(csrf_token);
} catch (exception) {
// 如果已經(jīng)登陸羡藐,則302贩毕,redirect to home
// alert(exception); // exception: TypeError: Cannot read property '1' of null
alert('登錄異常,請重新登錄');
return window.location = '/logout';
}
this.$http.post('/login',
//body
{
email: this.username,
password: this.password,
csrf_token: csrf_token
},
//options
{
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
}).then((response) => {
// 響應成功回調
var jsondata = response.body;
alert(JSON.stringify(jsondata));
this.token = jsondata.response.user.authentication_token;
this.is_login = true;
// alert('token:\n'+ this.token);
var userData = {
'username': this.username,
'token': this.token
};
window.localStorage.setItem("user", JSON.stringify(userData));
// this.$nextTick(function () { });
// getSubscription()
}, (response) => {
// 響應Login-POST錯誤回調
alert('登錄出錯了仆嗦! ' + response.status + response.statusText)
});
}, (response) => {
// 響應login-GET 錯誤回調
alert('登錄出錯了(CSRF)辉阶! ' + JSON.stringify(response))
});
},
注冊函數(shù)register(),跟login()類似,也需要先獲取csrf_token
register() {
if (!this.validation) return;
// get CSRF
var csrf_token = '';
this.$http.get('/register').then((response) => {
// 響應成功回調
var data = response.body;
// <input id="csrf_token" name="csrf_token" type="hidden" value="1483433916##5b057abdef66da070c8385752b78f6c584f6ba41"><input
var csrf_token = data.match(/name="csrf_token" type="hidden" value="(.*?)">/)[1]
// alert(csrf_token);
this.$http.post('/register',
//body
{
email: this.username,
password: this.password,
csrf_token: csrf_token
},
//options
{
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
}).then((response) => {
// 響應成功回調
var data = response.body;
// alert('Server rsp:\n'+ JSON.stringify(response));
//"body":{"meta":{"code":400},"response":{"errors":{"email":["aaa@bbb.com is already associated with an account."]}}},
if (data.meta.code !== 200) {
return alert(JSON.stringify(data.response.errors))
}
this.token = data.response.user.authentication_token;
this.is_login = true;
// alert(this.token);
var userData = {
'username': this.username,
'token': this.token
};
window.localStorage.setItem("user", JSON.stringify(userData));
}, (response) => {
// 響應錯誤回調
alert('注冊出錯了睛藻! ' + JSON.stringify(response))
});
}, (response) => {
// 響應register-GET 錯誤回調
alert('注冊出錯了(CSRF)启上! ' + JSON.stringify(response))
});
},
如果返回代碼不是200邢隧,則說明注冊出錯店印,比如用戶名已被占用,進一步倒慧,可由vue來產(chǎn)生中文友好的提示信息:
注銷函數(shù)logout():GET /logout
就行
logout() {
this.$http.get('/logout').then((response) => {
// 響應成功回調
this.is_login = false;
this.password = '';
this.token = '';
window.localStorage.removeItem("user")
}, (response) => {
// 響應錯誤回調
alert('Logout出錯了按摘! ' + JSON.stringify(response))
});
},
好了,更新不算麻煩纫谅,以后就一勞永逸了炫贤,session、token管理付秕、email驗證兰珍、密碼找回等等功能都有了。
Demo:http://vue2.heroku.com
源碼:https://github.com/kevinqqnj/vue-tutorial
請使用新的template: https://github.com/kevinqqnj/flask-template-advanced