簡體   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