Flask-Security 如何Session CSRF和REST Token兼得

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 CSRF和REST Token兼得》 Flask-Security.png

功能

先浏览一下Flask-Security官网所列的功能:

  • 登录追踪 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:
<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

服务器检查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来行

  1. 保存为app.py,然后运行python app.py
  2. 浏览器Session尝试:http://localhost:5000/loginhttp://localhost:5000/logouthttp://localhost:5000/register
  3. 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 '<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:
后台管理,一般用Flask-Admin。它也可以由Flask-Security来保护
My source code

参考:
Flask-Admin Use Flask-Security to authenticate users
Token Based Authentication with Flask-Security

    原文作者:非梦nj
    原文地址: https://www.jianshu.com/p/882070fad1fb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞