繁体   English   中英

如何在 Flask 中实现基于角色的访问控制?

[英]How to implement role based access control in Flask?

是否有任何积极维护的插件可以帮助我创建具有基于角色的访问控制的 Flask 应用程序? 例如管理员角色、会计角色、人力资源角色...

Flask-User看起来不错,但这些讨论表明维护者已经走了…… https://gitter.im/Flask-User/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge

Flask-Login需要Flask-Security ,它是无人维护的,但也有Flask-Security-Too 后者通过Flask-Principal实现授权,最后一个版本是在 2013 年 - 对我来说看起来很死( https://github.com/mattupstate/flask-principal/issues/50 )。

感谢您的任何建议。

我在同一个旅程..你可以看看 flask-RBAC => https://flask-rbac.readthedocs.io/其中 RBAC 代表“基于角色的访问控制”......

最后一次提交不到 30 天前

我不够专业,不能说它是否比您引用的更好,但是使用如此明确的名称可能值得。 让我知道你对此有何看法。

(PS:无论如何我都不是这个项目的附属机构)

也有可能没有一个解决方案是 100% 合适的,即您将需要更改或扩展。

在这种情况下,我的建议是最好从基本模型开始,然后根据您的需要进行扩展。 一些插件的想法和方法可以帮助您解决问题。 这种方法的优点是您将更多地了解幕后发生的事情,这与从某些插件导入不同。

插件很棒,为扩展社区付出了很多努力。 没有两难境地。

以答案为动力,因为您的问题可以用最少的代码启动 0.0.1 版:

class RolesUsers(Base):
    __tablename__ = 'roles_users'

    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
    role_id = db.Column(db.Integer(), db.ForeignKey('role.id'))

class Role(RoleMixin, Base):
    __tablename__ = 'role'

    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)

    def __repr__(self):
        return self.name

class User(UserMixin, Base):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), index=True, unique=True)

    roles = db.relationship('Role', secondary='roles_users',
                            backref=db.backref('users', lazy='dynamic'))

M2M

更新:评论请求

现在您有一个代表上述第一个版本 0.0.1 的迷你应用程序 :) 由于您有一段时间没有使用 FLASK,所以我尽力通过评论提醒您一些基本细节。

import datetime
from functools import wraps
from flask import Flask, redirect, url_for, session, render_template_string
from flask_sqlalchemy import SQLAlchemy


# Class-based application configuration
class ConfigClass(object):
    """ Flask application config """

    # Flask settings
    SECRET_KEY = 'This is an INSECURE secret!! DO NOT use this in production!!'

    # Flask-SQLAlchemy settings
    SQLALCHEMY_DATABASE_URI = 'sqlite:///db.sqlite'    # File-based SQL database
    SQLALCHEMY_TRACK_MODIFICATIONS = False    # Avoids SQLAlchemy warning

def create_app():
    """ Flask application factory """
    
    # Create Flask app load app.config
    app = Flask(__name__)
    app.config.from_object(__name__+'.ConfigClass')

    # Initialize Flask-SQLAlchemy
    db = SQLAlchemy(app)

    @app.before_request
    def before_request():
        try:
            print("Current Role: ", session['role'])
        except:
            print("Current Role: Guest")

    # Define the User data-model.
    class User(db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1')

        # User authentication information. The collation='NOCASE' is required
        # to search case insensitively when USER_IFIND_MODE is 'nocase_collation'.
        email = db.Column(db.String(255, collation='NOCASE'), nullable=False, unique=True)
        email_confirmed_at = db.Column(db.DateTime())
        password = db.Column(db.String(255), nullable=False, server_default='')

        # User information
        first_name = db.Column(db.String(100, collation='NOCASE'), nullable=False, server_default='')
        last_name = db.Column(db.String(100, collation='NOCASE'), nullable=False, server_default='')

        # Define the relationship to Role via UserRoles
        roles = db.relationship('Role', secondary='user_roles')

    # Define the Role data-model
    class Role(db.Model):
        __tablename__ = 'roles'
        id = db.Column(db.Integer(), primary_key=True)
        name = db.Column(db.String(50), unique=True)

    # Define the UserRoles association table
    class UserRoles(db.Model):
        __tablename__ = 'user_roles'
        id = db.Column(db.Integer(), primary_key=True)
        user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
        role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))

    # First Drop then Create all database tables
    db.drop_all()
    db.create_all()

    # Create 'member@example.com' user with no roles
    if not User.query.filter(User.email == 'member@example.com').first():
        user = User(
            email='member@example.com',
            email_confirmed_at=datetime.datetime.utcnow(),
            password='Password1',
        )
        db.session.add(user)
        db.session.commit()

    # Create 'admin@example.com' user with 'Admin' and 'Agent' roles
    if not User.query.filter(User.email == 'admin@example.com').first():
        user = User(
            email='admin@example.com',
            email_confirmed_at=datetime.datetime.utcnow(),
            password='Password1',
        )
        user.roles.append(Role(name='Admin'))
        user.roles.append(Role(name='Member'))
        db.session.add(user)
        db.session.commit()

    def access_required(role="ANY"):
        """
        see: https://flask.palletsprojects.com/en/2.1.x/patterns/viewdecorators/
        """
        def wrapper(fn):
            @wraps(fn)
            def decorated_view(*args, **kwargs):
                if session.get("role") == None or role == "ANY":
                    session['header'] = "Welcome Guest, Request a new role for higher rights!"
                    return redirect(url_for('index'))
                if session.get("role") == 'Member' and role == 'Member':
                    print("access: Member")
                    session['header'] = "Welcome to Member Page!"
                    return redirect(url_for('index'))
                if session.get("role") == 'Admin' and role == 'Admin':
                    session['header'] = "Welcome to Admin Page!"
                    print("access: Admin")
                else:
                    session['header'] = "Oh no no, you haven'tn right of access!!!"
                    return redirect(url_for('index'))
                return fn(*args, **kwargs)
            return decorated_view
        return wrapper

    # The index page is accessible to anyone
    @app.route('/')
    def index():
        print("index:", session.get("role", "nema"))
        return render_template_string('''
                <h3>{{ session["header"] if session["header"] else "Welcome Guest!" }}</h3>
                <a href="/admin_role">Get Admin Role</a> &nbsp&nbsp | &nbsp&nbsp
                <a href="/admin_page">Admin Page</a> <br><br>
                <a href="/member_role">Get Member Role</a> &nbsp&nbsp | &nbsp&nbsp
                <a href="/member_page">Member Page</a> <br><br>
                <a href="/guest_role">Get Guest Role</a> <br><br>
                <a href="/data">Try looking at DATA with different roles</a>
            ''')

    @app.route('/data')
    def data():
        return render_template_string("""
                <a href="/admin_role">Admin Role</a>
                <a href="/member_role">Member Role</a>
                <a href="/guest_role">Guest Role</a>
                <br><p>The data page will display a different context depending on the access rights.</p><br> 
                {% if not session['role'] %}
                    <h2>You must have diffrent role for access to the data.</h2>
                    <a href="{{ url_for('.index') }}">Go back?</a>
                {% endif %}

                {% if session['role'] == 'Member' or  session['role'] == 'Admin' %}
                <h2>USERS:</h2>
                <table>
                <tr>
                    <th>ID</th>
                    <th>EMAIL</th>
                </tr>
                {% for u in users %}
                <tr>
                    <td>{{ u.id }}</td>
                    <td>{{ u.email }}</td>
                </tr>
                {% endfor %}
                </table>
                {% endif %}

                {% if session['role'] == 'Admin' %}
                    <h2>ROLE:</h2>
                    <table>
                    <tr>
                        <th>ID</th>
                        <th>NAME</th>
                    </tr>
                    {% for r in roles %}
                    <tr>
                        <td>{{ r.id }}</td>
                        <td>{{ r.name }}</td>
                    </tr>
                    {% endfor %}
                    </table>

                    <h2>USER ROLES:</h2>
                    <table>
                    <tr>
                        <th>ID</th>
                        <th>USER ID</th>
                        <th>ROLE ID</th>
                    </tr>
                    {% for ur in user_roles %}
                    <tr>
                        <td>{{ ur.id }}</td>
                        <td>{{ ur.user_id }}</td>
                        <td>{{ ur.role_id }}</td>
                    </tr>
                    {% endfor %}
                    </table>
                {% endif %}
            """, 
                users=User.query, 
                roles= Role.query, 
                user_roles=UserRoles.query)

    @app.route("/member_role")
    def member_role():
        """
        Anyone can access the url and get the role of a MEMBER.
        """
        r = Role.query.get(2)
        session['role'] = r.name
        session['header'] = "Welcome to Member Access!"
        return render_template_string('<h2>{{ session["header"] }}</h2> <a href="/">Go back?</a>')

    @app.route("/member_page")
    @access_required(role="Member")
    def member_page():
        # session['header'] = "Welcome to Admin Page!"
        return render_template_string('<h1>{{ session["header"] }}</h1> <a href="/">Go back?</a>')

    @app.route("/admin_role")
    def admin_role():
        """
        Anyone can access the url and get the role of a ADMIN.
        """
        r = Role.query.get(1)
        session['role'] = r.name
        session['header'] = "Welcome to Admin Access!"
        return render_template_string('<h1>{{ session["header"] }}</h1> <a href="/">Go back?</a>')

    @app.route("/admin_page")
    @access_required(role="Admin")
    def admin_page():
        """
        This url requires an ADMIN role.
        """
        return render_template_string('<h1>{{ session["header"] }}</h1> <a href="/">Go back?</a>')

    @app.route("/guest_role")
    def guest_role():
        """
        For the GUEST, we will only delete the session and thus 'kill' the roles.
        """
        session.clear()
        return redirect(url_for('index'))

    return app


# Start development web server
if __name__ == '__main__':
    app = create_app()
    app.run(host='0.0.0.0', port=5000, debug=True)

现在我们可以玩了,因为有一些规则,这些规则是:只有当您具有管理员角色时才能访问管理页面,这同样适用于成员和他的页面。 此外,您还有一个数据列表,这些数据构成了答案的第一个版本中提到的模型。 这个数据列表也可以在不同的角色中看到。 详细信息:如果您有管理员角色,那么您将能够看到要显示的所有数据,如果您有成员角色,那么您只能看到用户,当然,如果您是管理员,您将无法看到任何数据来宾。

这里是图片形式。

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

现在我希望这个简单的示例将有助于您和 stackcoverflow 社区的其他成员进一步学习。

我坚信你很快就会有一个更好的版本。

快乐的编码...

嘿,如果您不启动项目,可以查看Flask-Authorize

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM