简体   繁体   English

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

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

Are there any actively maintained plugins out there which would help me create a Flask app with role based access control?是否有任何积极维护的插件可以帮助我创建具有基于角色的访问控制的 Flask 应用程序? eg admin role, accounting role, hr role...例如管理员角色、会计角色、人力资源角色...

Flask-User looks good, but these discussions indicate the maintainer is gone... https://gitter.im/Flask-User/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge Flask-User看起来不错,但这些讨论表明维护者已经走了…… https://gitter.im/Flask-User/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge

Flask-Login needs Flask-Security , which is unmaintained, but there is Flask-Security-Too . Flask-Login需要Flask-Security ,它是无人维护的,但也有Flask-Security-Too The latter implements authorization via Flask-Principal , which last release was in 2013 - looks pretty dead to me ( https://github.com/mattupstate/flask-principal/issues/50 ).后者通过Flask-Principal实现授权,最后一个版本是在 2013 年 - 对我来说看起来很死( https://github.com/mattupstate/flask-principal/issues/50 )。

Thanks for any recommendations.感谢您的任何建议。

I'm on the same journey.. You may have a look at flask-RBAC => https://flask-rbac.readthedocs.io/ where RBAC just stands for "Role Based Access Control"...我在同一个旅程..你可以看看 flask-RBAC => https://flask-rbac.readthedocs.io/其中 RBAC 代表“基于角色的访问控制”......

the last commit is less than 30 days ago最后一次提交不到 30 天前

I'm not expert enough to say if it is better than the ones you cites but it may worth it with such an explicit name.我不够专业,不能说它是否比您引用的更好,但是使用如此明确的名称可能值得。 Let me know what you think off it.让我知道你对此有何看法。

(PS : i'm not affiliate in anyway with this project) (PS:无论如何我都不是这个项目的附属机构)

It is also possible that none of the solutions will be 100% appropriate, ie that you will need changes or extensions.也有可能没有一个解决方案是 100% 合适的,即您将需要更改或扩展。

In such situations, my suggestion is best to start with, say, the basic model and expand it exactly to your needs.在这种情况下,我的建议是最好从基本模型开始,然后根据您的需要进行扩展。 And the ideas and approaches of some plugins can help you with your solution.一些插件的想法和方法可以帮助您解决问题。 The advantage of this approach is that you will learn much more about what is happening under the hood, unlike that one import from some plugins.这种方法的优点是您将更多地了解幕后发生的事情,这与从某些插件导入不同。

Plugins are great, a lot of effort has been put into expanding the community.插件很棒,为扩展社区付出了很多努力。 There is no dilemma.没有两难境地。

Take the answer as motivation because your problem can start version 0.0.1 with minimal code:以答案为动力,因为您的问题可以用最少的代码启动 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'))

See M2MM2M

UPDATE: Request from comment更新:评论请求

Now you have one mini application that represents the mentioned first version 0.0.1 :) Since you don't use FLASK for a while, I did my best to remind you of some basic details through the comments.现在您有一个代表上述第一个版本 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)

And now we can play because there are certain rules and these are: that the admin page can be accessed only if you have an admin role, the same applies to the member and his page.现在我们可以玩了,因为有一些规则,这些规则是:只有当您具有管理员角色时才能访问管理页面,这同样适用于成员和他的页面。 Also, you have a list of data that makes up the mentioned model from the first version of the answer.此外,您还有一个数据列表,这些数据构成了答案的第一个版本中提到的模型。 This list of data can also be seen with different roles.这个数据列表也可以在不同的角色中看到。 Details: if you have an admin role then you will be able to see all the data to be displayed, if you have a member role then you can only see users and of course you will not be able to see any data if you are a guest.详细信息:如果您有管理员角色,那么您将能够看到要显示的所有数据,如果您有成员角色,那么您只能看到用户,当然,如果您是管理员,您将无法看到任何数据来宾。

Here it is in pictorial form.这里是图片形式。

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

I hope now that this simple example will be helpful in further learning for both you and other members of the stackcoverflow community.现在我希望这个简单的示例将有助于您和 stackcoverflow 社区的其他成员进一步学习。

And I firmly believe that you will soon have a much better version.我坚信你很快就会有一个更好的版本。

Happy coding ...快乐的编码...

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

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

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