Flask-Principal应用场景解析

Flask-Principal作为一个Flask扩展的框架,首先,要了解包含的四个组件:Identity,Needs,Permission,IdentityContext。

  • Identity(身份)

源码解释:

Represent the user's identity.
:param id: The user id
:param auth_type: The authentication type used to confirm the user's identity.
The identity is used to represent the user's identity in the system. This
object is created on login, or on the start of the request as loaded from
the user's session.
Once loaded it is sent using the identity-loaded signal, and should be
populated with additional required information.
Needs that are provided by this identity should be added to the provides
set after loading.

Identity代码一个用户,每一次的请求,用户的信息(id,auth_type)都会被记录在session中,它包含着用户拥有的访问的权限。一旦被加载,它将使用identity-loaded的信号发送,然后填充了其他所需信息

  • Needs(需求)

Need 是访问控制的最小粒度,并代表特殊的操作权限。 如 “管理员角色”,“可以编辑博客帖子”
用户可以灵活设计很多自定义的需求,它的值是以(method,value)元组形式定义,比如:('role', 'admin')、('role', 'Editor')、('user','zhangsan')、('user','lisi')等等

Need = namedtuple('Need', ['method', 'value'])
UserNeed = partial(Need, 'id')
UserNeed.__doc__ = """A need with the method preset to `"id"`."""


RoleNeed = partial(Need, 'role')
RoleNeed.__doc__ = """A need with the method preset to `"role"`."""


TypeNeed = partial(Need, 'type')
TypeNeed.__doc__ = """A need with the method preset to `"type"`."""


ActionNeed = partial(Need, 'action')
TypeNeed.__doc__ = """A need with the method preset to `"action"`."""


ItemNeed = namedtuple('ItemNeed', ['method', 'value', 'type'])
"""A required item need
  • Permission (权限)

用一个set表示,包含了对资源的访问控制。 Permission 其实是通过 Needs 来定义和初始化的, 其中 Permission 可以是一个权限的集合.

admin_permission = Permission(RoleNeed('admin'))
admin_permission = Permission(RoleNeed('editor'))
admin_permission = Permission(RoleNeed('reader'))
  • IdentityContext (身份上下文)

IdentityContext 是针对某一特定身份和某一特定权限的上下文环境,可以作为 context manager 或者 decorator 使用。

应用场景:

1.用于保护资源的访问

比如blog管理员界面只有拥有admin权限的用户才可以访问,或者管理blog所有文章界面,可以通过decorator 和context manager。
实例:

from flask import Flask, Response
from flask_principal import Principal, Permission, RoleNeed

app = Flask(__name__)
principals = Principal(app)

# 创建定义为'admin'的权限
admin_permission = Permission(RoleNeed('admin'))

# 使用admin_permission.require()作为decorator装饰视图保护是否有相应的权限
@app.route('/admin')
@admin_permission.require()
def do_admin_index():
    return Response('Only if you are an admin')

# this time protect with a context manager
@app.route('/articles')
def do_articles():
    with admin_permission.require():
        return Response('Only if you are admin')
2.用于实现身份改变信号逻辑

Flask-principle结合Flask-Login,当登录用户改变时,认证 providers 应该使用 identity-changed 信号来表明这个请求已经被认证。
实例:

from flask import Flask, current_app, request, session
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_wtf import FlaskForm, TextField, PasswordField, Required, Email
from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed

app = Flask(__name__)

Principal(app)
login_manager = LoginManager(app)

@login_manager.user_loader
def load_user(userid):
    # 返回一个User实例
    return datastore.find_user(id=userid)

class LoginForm(FlaskForm):
    email = TextField()
    password = PasswordField()

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = datastore.find_user(email=form.email.data)
        if form.password.data == user.password:

            # Flask-Login的login_user方法将登录用户信息保存于session中
            login_user(user)

            # 当前应用对象 app 和当前要登录的用户对象作为身份对象, 以信号的形式发送出去
            identity_changed.send(current_app._get_current_object(),
                                  identity=Identity(user.id))

            return redirect(request.args.get('next') or '/')

    return render_template('login.html', form=form)


@app.route('/logout')
@login_required
def logout():
    # 登出时删除保存在session中的用户信息
    logout_user()

    # 删除Flask-Principal设置的会话密钥
    for key in ('identity.name', 'identity.auth_type'):
        session.pop(key, None)

    # 告诉Flask-Principal用户是匿名的
    identity_changed.send(current_app._get_current_object(),
                          identity=AnonymousIdentity())

    return redirect(request.args.get('next') or '/')
3.用于实现权限载入信号逻辑

与上面例子想结合,当用户登录后,通过identity_loaded添加一切附加信息到 Identity 实例,然后改变权限信息
实例:

from flask_login import current_user
from flask_principal import identity_loaded, RoleNeed, UserNeed

@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user

    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))

    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))

on_identity_loaded() 函数在用户身份发生了变化, 需要重载权限的时候被调用. 首先将当前的用户绑定到一个 Identity 的实例化对象中, 然后将该用户 id 的 UserNeed 和该用户所拥有的 roles 对应的 RoleNeed 绑定到该 Identity 中. 实现了将数据库中 user 所拥有的 roles 都以 Needs 的形式绑定到其自身中.

4.用于实现权限保护

举个例子:要实现只有博客帖子的作者才能编辑文章。这个可以通过创建必需的 Need 和
Permission 对象来实现,并且添加更多逻辑到 identity_loaded 信号函数上
实例:

from collections import namedtuple
from functools import partial

from flask_login import current_user
from flask_principal import identity_loaded, Permission, RoleNeed, \
     UserNeed

BlogPostNeed = namedtuple('blog_post', ['method', 'value'])
EditBlogPostNeed = partial(BlogPostNeed, 'edit')

class EditBlogPostPermission(Permission):
    def __init__(self, post_id):
        need = EditBlogPostNeed(unicode(post_id))
        super(EditBlogPostPermission, self).__init__(need)

@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user

    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))

    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))

    # Assuming the User model has a list of posts the user
    # has authored, add the needs to the identity
    if hasattr(current_user, 'posts'):
        for post in current_user.posts:
            identity.provides.add(EditBlogPostNeed(unicode(post.id)))

接下来,保护这个endpoint允许某个用户编辑一篇文章,这是通过使用资源ID即时创建权限(Permission)对象来进行的 ,在这个博客文章例子中:

@app.route('/posts/<post_id>', methods=['PUT', 'PATCH'])
def edit_post(post_id):
    permission = EditBlogPostPermission(post_id)

    if permission.can():
        # Save the edits ...
        return render_template('edit_post.html')

    abort(403)  # HTTP Forbidden
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • wyun_guest需要添加的地方 1: oauth_clients需要添加一条记录image.png INSER...
    EddieZhang阅读 677评论 0 0
  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,180评论 22 257
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,322评论 0 10
  • 准备工具: 中华HB铅笔、晨光0.38mm中性笔、白雪0.5mm走珠笔 第一步:先用HB铅笔画出大致轮廓,再用0....
    拾乐者阅读 620评论 0 2