介绍
Flask-Principal提供了非常松散的框架,以绑定两种类型服务机制,它们通常位于一个web应用程序的不同部分。
- 权限认证机制
- 用户信息管理机制
例如,一个权限认证机制采用 OAuth,使用Flask-OAuth,用户信息存储在关系数据库中。框架使用signal消息作为应用接口(松耦合)。
框架主要部分包含身份(Identity),需求(Needs),权限(Permission),和包含身份信息上下文环境(IdentityContext)
- Identity代表用户,并为每个请求(例如会话)保存/加载不同位置。对系统来说,身份是用户的化身。它包含了用户的访问权限。
2.Need是最小原子的访问控制,并代表一个特定参数的情况。例如“管理角色”,“可以编辑博客”。 - Permission是任何时候访问资源,都应该出现的功能。
- IdentityContext是某种身份的背景下针对特定的许可。它可以用于作为一个上下文管理器(context manager)或装饰(decorator)。
保护访问的资源
对用户(不是身份验证机制)进行访问限制很容易使用,可以作为装饰或上下文管理器,下面是示例。
from flask import Flask, Response
from flask.ext.principal import Principal, Permission, RoleNeed
app = Flask(__name__)
# load the extension
principals = Principal(app)
# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = Permission(RoleNeed('admin'))
# protect a view with a principal for that need
@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')
权限认证机制
身份验证机制应该使用identity-changed
信号表明请求已经被验证过。例如,下面的代码是一个例子,如何将流行的Flask-Login与Flask-Principal扩展:
from flask import Flask, current_app, request, session
from flask.ext.login import LoginManager, login_user, logout_user, \
login_required, current_user
from flask.ext.wtf import Form, TextField, PasswordField, Required, Email
from flask.ext.principal import Principal, Identity, AnonymousIdentity, \
identity_changed
app = Flask(__name__)
Principal(app)
login_manager = LoginManager(app)
@login_manager.user_loader
def load_user(userid):
# Return an instance of the User model
return datastore.find_user(id=userid)
class LoginForm(Form):
email = TextField()
password = PasswordField()
@app.route('/login', methods=['GET', 'POST'])
def login():
# A hypothetical login form that uses Flask-WTF
form = LoginForm()
# Validate form input
if form.validate_on_submit():
# Retrieve the user from the hypothetical datastore
user = datastore.find_user(email=form.email.data)
# Compare passwords (use password hashing production)
if form.password.data == user.password:
# Keep the user info in the session using Flask-Login
login_user(user)
# Tell Flask-Principal the identity changed
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():
# Remove the user information from the session
logout_user()
# Remove session keys set by Flask-Principal
for key in ('identity.name', 'identity.auth_type'):
session.pop(key, None)
# Tell Flask-Principal the user is anonymous
identity_changed.send(current_app._get_current_object(),
identity=AnonymousIdentity())
return redirect(request.args.get('next') or '/')
用户信息管理机制
用户信息管理机制应该接收identity-loaded
信号(signal),对身份实例添加任何信息,比如角色(roles)。下面是另一个使用Flask-Login和连接上面代码的例子。它是一个可使用的基于角色的权限方案。
from flask.ext.login import current_user
from flask.ext.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))
你可以把上面代码放在create_app
里。
细粒度的资源保护
现在比如,你仅仅想作者可以发布和编辑文章,这个需要创建Need
和Permission
对象,并对identity_loaded
信号处理程序添加更多的逻辑。例如:
from collections import namedtuple
from functools import partial
from flask.ext.login import current_user
from flask.ext.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(post_id)创建一个许可对象实例:
@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