覆盖Flask-Security' s / login端点

时间:2016-05-05 22:16:58

标签: python oauth flask flask-security

我在用户通过OAuth登录的网站上工作,而不是基于密码的系统。

因此,Flask-Security的默认登录页面实际上并不适用于我的用例,因为我需要/login端点来进行OAuth设置。我能够通过更改/login设置选项,使我的SECURITY_LOGIN_URL路由没有被Flask-Security覆盖。

这一切正常,OAuth登录页面显示并返回所需的所有信息。

问题出现了,因为我也尝试使用@login_required装饰器。

如果用户未登录,则/login装饰器会重定向到我的@login_required页面,而不是重定向到Flask-Security的页面。

显然,配置端点在这种情况下没有帮助。

是否可以强制Flask-Security使用我的登录路由(OAuth)而不是其页面?

这是一些示例代码,显示了我正在讨论的Flask-Security覆盖定义的路线:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, request
from flask_security import (Security, SQLAlchemyUserDatastore,
                            UserMixin, RoleMixin, login_required,
                            login_user, logout_user, current_user)
from passlib.context import CryptContext
import os


class Config:
    basedir = os.path.abspath(os.path.dirname(__file__))

    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    DEBUG = True
    PORT = 8000
    HOST = "0.0.0.0"

    SECRET_KEY = "foobar123"


config = Config()


app = Flask(__name__, instance_relative_config=True)

app.config.from_object(config)

db = SQLAlchemy(app)

lm = LoginManager()

lm.init_app(app)
lm.login_view = "login"


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 User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))
    provider_id = db.Column(db.String(64), index=True, unique=True)

    @property
    def is_authenticated(self):
        return True

    @property
    def is_active(self):
        return True

    @property
    def is_anonymous(self):
        return True

    def get_id(self):
        return str(self.id)

    def hash_password(self, password):
        self.hashed_password = pwd_context.encrypt(password)

    def verify_password(self, password):
        return pwd_context.verify(password, self.hashed_password)


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))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

pwd_context = CryptContext(
    schemes=["bcrypt", "pbkdf2_sha256", "des_crypt"],
    default="bcrypt",
    all__vary_rounds=0.1
)


@lm.user_loader
def load_user(id):
    return User.query.get(int(id))


@app.route("/")
@app.route("/index")
@login_required
def index():
    """Handles calls to / and /index, return the panel"""
    return render_template("index.html")


@app.route("/login")
def login():
    """Would include a bunch of OAuth stuff, not required for this example
    If you try going to this endpoint, you'll get the Flask-Security /login
    instead."""
    return render_template("login.html")

app.run(debug=True)

2 个答案:

答案 0 :(得分:2)

有趣的是,我今天遇到了一个非常类似的问题。 如果我设法以某种方式解决它,我会考虑优雅的SO答案,我会更新这个。与此同时,我对解决问题的策略的看法是:

  1. 子类瓶安全性并仅重载@login_required装饰器以将您重定向到正确的位置。如果重定向是您遇到的唯一问题,可能最快。
  2. 如果您只使用Oauth,请使用替代装饰器替换@login_requiredFlask-OAuthlib是一个有用的誓言库,documents show you how to protect a resource使用`@ oauth.require_oauth'装饰器。
  3. Flask-Stormpath是一个商业解决方案,我不太熟悉它是否涵盖了这个特定的理由,因此我不建议将其作为一种可能的方法。但是for background reading他们对与Flask相关的认证大黄蜂巢有一个有用的概述。

答案 1 :(得分:0)

您可能希望覆盖原始Flask-Security的登录视图。像这样:

id: 3

就是这样。如果您想使用Flask-Security的原始HTML模板,您还需要覆盖它(请参阅Flask-Security's documentation)。在那里,您只需放置自己的视图而不是旧视图:

my_blueprint = Blueprint('my_blueprint', __name__)

@bp.route('/login', methods=['GET', 'POST'])
@anonymous_user_required
def my_login_view():
    # Do whatever you want to do here. Try to find inspiration in the original code

app = Flask(__name__)
app.register_blueprint(my_blueprint)

# You will need to define User and Role classes, see Flask-Security's documentation
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)