How to use both CSRF and auth Token in Flask-Security
以前学习的《Flask Web开发:基于Python的Web应用开发实战》,用到了
Flask-Login
,管理用户Session、Cookie
我们的应用:Vue 2.0 起步(4) 轻量级后端Flask用户认证 - 微信公众号RSS,用到了Flask-JWT
,管理REST访问用的Token
用户权限管理,像Admin/User/Editor,是用Flask-Principal
当然,还有E-mail验证、密码修改。。。
如果我们的应用,即想管理Session(网页),又想要管理Token(REST访问),是不是两者不能兼得?抑或很繁琐呢?
幸好,Flask用于鉴权管理,有个集大成者:Flask-Security
功能
先浏览一下Flask-Security官网所列的功能:
会话管理 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.权限分类 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.基本网页HTTP鉴权 Basic HTTP Authentication
use e-mail as 'username'Token鉴权 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。邮件验证 Email Confirmation
密码重置 Password Reset/Recovery
用户注册 User Registration
-
登录追踪 Login Tracking
- 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
是不是眼花缭乱了?哈哈,你能想到的和想不到的,Flask-Security都帮你做到了。
注意看功能列表最后一段:JSON访问必须也带上CSRF。这跟我们以前用的Flask-JWT是不一样的哦!
下面我们来使用Flask-Security,实现Session CSRF和REST Token兼得的效果!
CSRF跨站请求伪造(Cross-site request forgery)
也被称为one-click attack 或者session riding,通常缩写为CSRF或者XSRF, 是一种挟制用户在当前已登录的Web应用程序上,执行非本意的操作的攻击方法。
因为表单登录以后,会保存session信息到用户电脑。如果使用了“Remember me”功能,会保存session_token到用户cookie到用户电脑,那更加危险!攻击者拿到这个cookie,就能直接访问了。
对于web站点,将持久化的授权方法(例如cookie或者HTTP授权)切换为瞬时的授权方法(一般是在每个form中提供隐藏csrf_token field),这将帮助网站防止这些攻击。
步骤:
1. get csrf_token
正常浏览器登录,都会用到表单(Forms),表单里带有隐藏的CSRF。所以,如果REST访问,我们先拿到这个CSRF
# ajax send request:
GET http://localhost:5000/login
# Server response:
Login
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":"[email protected]",
"password":"password",
"csrf_token":"34c942cc61f0bxxx"
}
3. get auth_token
服务器检查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访问,带上它,就能访问服务器端@auth_token_required
保护的所有路由了
# ajax send request:
GET http://localhost:5000/token_protected
Headers: Authentication-Token WyIxIiwiNWY0ZGNjM2I1Yxxx
到此,我们Server端,即可以管理Session(网页),又可以管理Token(REST访问)了!
Quickstart源码
源码很短,60来行
- 保存为app.py,然后运行
python app.py
- 浏览器Session尝试:http://localhost:5000/login、http://localhost:5000/logout、http://localhost:5000/register
- JSON Token尝试:使用Curl或浏览器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 '' % 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='[email protected]', 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:
后台管理,一般用Flask-Admin。它也可以由Flask-Security来保护
My source code
参考:
Flask-Admin Use Flask-Security to authenticate users
Token Based Authentication with Flask-Security