How to use both CSRF and auth Token in Flask-Security
以前學(xué)習(xí)的《Flask Web開發(fā):基于Python的Web應(yīng)用開發(fā)實(shí)戰(zhàn)》呕童,用到了
Flask-Login
,管理用戶Session、Cookie
我們的應(yīng)用:Vue 2.0 起步(4) 輕量級(jí)后端Flask用戶認(rèn)證 - 微信公眾號(hào)RSS急鳄,用到了Flask-JWT
妈经,管理REST訪問用的Token
用戶權(quán)限管理汉矿,像Admin/User/Editor受裹,是用Flask-Principal
當(dāng)然西傀,還有E-mail驗(yàn)證事扭、密碼修改捎稚。。句旱。
如果我們的應(yīng)用阳藻,即想管理Session(網(wǎng)頁(yè)),又想要管理Token(REST訪問)谈撒,是不是兩者不能兼得腥泥?抑或很繁瑣呢?
幸好啃匿,F(xiàn)lask用于鑒權(quán)管理蛔外,有個(gè)集大成者:Flask-Security
功能
先瀏覽一下Flask-Security官網(wǎng)所列的功能:
會(huì)話管理 Session Based Authentication
Session based authentication is fulfilled entirely by the Flask-Login
extension. Also uses Flask-Login’s alternative token feature for remembering users when their session has expired.權(quán)限分類 Role/Identity Based Access
you can associate a high level role or multiple roles to any user.密碼加密 Password Encryption
Password encryption is enabled with passlib.基本網(wǎng)頁(yè)HTTP鑒權(quán) Basic HTTP Authentication
use e-mail as 'username'Token鑒權(quán) Token Authentication
Token based authentication is enabled by retrieving the user auth token by performing an HTTP POST with the authentication details as JSON data。
-
- Last login date
- Current login date
- Last login IP address
- Current login IP address
- Total login count
-
JSON/Ajax Support
Flask-Security supports JSON/Ajax requests where appropriate. Just remember that all endpoints require a CSRF token just like HTML views.
JSON is supported for the following operations:- Login requests
- Registration requests
- Change password requests
- Confirmation requests
- Forgot password requests
- Passwordless login requests
是不是眼花繚亂了溯乒?哈哈夹厌,你能想到的和想不到的,F(xiàn)lask-Security都幫你做到了裆悄。
注意看功能列表最后一段:JSON訪問必須也帶上CSRF矛纹。這跟我們以前用的Flask-JWT是不一樣的哦!
下面我們來使用Flask-Security光稼,實(shí)現(xiàn)Session CSRF和REST Token兼得的效果或南!
CSRF跨站請(qǐng)求偽造(Cross-site request forgery)
也被稱為one-click attack 或者session riding孩等,通常縮寫為CSRF或者XSRF采够, 是一種挾制用戶在當(dāng)前已登錄的Web應(yīng)用程序上肄方,執(zhí)行非本意的操作的攻擊方法。
因?yàn)楸韱蔚卿浺院蟮虐瑫?huì)保存session信息到用戶電腦权她。如果使用了“Remember me”功能,會(huì)保存session_token到用戶cookie到用戶電腦逝薪,那更加危險(xiǎn)隅要!攻擊者拿到這個(gè)cookie,就能直接訪問了翼闽。
對(duì)于web站點(diǎn)拾徙,將持久化的授權(quán)方法(例如cookie或者HTTP授權(quán))切換為瞬時(shí)的授權(quán)方法(一般是在每個(gè)form中提供隱藏csrf_token field),這將幫助網(wǎng)站防止這些攻擊感局。
步驟:
1. get csrf_token
正常瀏覽器登錄尼啡,都會(huì)用到表單(Forms),表單里帶有隱藏的CSRF询微。所以崖瞭,如果REST訪問,我們先拿到這個(gè)CSRF
# ajax send request:
GET http://localhost:5000/login
# Server response:
<h1>Login</h1>
<form action="/login" method="POST" name="login_user_form">
<input id="csrf_token" name="csrf_token" type="hidden" value="34c942cc61f0bxxx">
...
# javascript fetch "csrf_token"
document.getElementById("csrf_token").value
2. post email/password
REST訪問撑毛,使用POST书聚,帶上email, password和上一步的csrf_token
# ajax send request:
POST http://localhost:5000/login
Headers: Content-Type application/json
Body:
{
"email":"aaa@bbb.com",
"password":"password",
"csrf_token":"34c942cc61f0bxxx"
}
3. get auth_token
服務(wù)器檢查CSRF是否有效,以及email/password是否匹配藻雌。成功則返回auth_token:
# Server response:
{
"meta": {
"code": 200
},
"response": {
"user": {
"authentication_token": "WyIxIiwiNWY0ZGNjM2I1Yxxx",
"id": "1"
}
}
}
4. access views with @auth_token_required
終于拿到auth_token了雌续!
下面的REST訪問,帶上它胯杭,就能訪問服務(wù)器端@auth_token_required
保護(hù)的所有路由了
# ajax send request:
GET http://localhost:5000/token_protected
Headers: Authentication-Token WyIxIiwiNWY0ZGNjM2I1Yxxx
到此驯杜,我們Server端,即可以管理Session(網(wǎng)頁(yè))做个,又可以管理Token(REST訪問)了鸽心!
Quickstart源碼
源碼很短,60來行
- 保存為app.py居暖,然后運(yùn)行
python app.py
- 瀏覽器Session嘗試:http://localhost:5000/login顽频、http://localhost:5000/logout、http://localhost:5000/register
- JSON Token嘗試:使用Curl或?yàn)g覽器REST插件太闺,來模擬Ajax GET/POST
# encoding: utf-8
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required, auth_token_required, http_auth_required
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SECURITY_TRACKABLE'] = True
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_SEND_REGISTER_EMAIL'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///.security-dev.sqlite'
# Create database connection object
db = SQLAlchemy(app)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=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'))
def __repr__(self):
return '<User %r>' % self.email
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create a user to test with
@app.before_first_request
def create_user():
db.create_all()
if not User.query.first():
user_datastore.create_user(email='aaa@bbb.com', password='password')
db.session.commit()
# Views
@app.route('/')
@login_required
def home():
return 'you\'re logged in!'
@app.route('/api')
#@http_auth_required
@auth_token_required
def token_protected():
return 'you\'re logged in by Token!'
if __name__ == '__main__':
app.run()
TODO:
后臺(tái)管理糯景,一般用Flask-Admin。它也可以由Flask-Security來保護(hù)
My source code
參考:
Flask-Admin Use Flask-Security to authenticate users
Token Based Authentication with Flask-Security