最近写CMDB的时候遇到了一个问题,那就是flask的权限问题,目前我了解到的Flask有3种方案进行权限管理的操作,
Flask Principal
先跳过第一种,可能纯属是我技术的原因吧,我觉得有轮子不用反复造的想法,所以就跳过了第一种选择了后面2种框架的,其实Flask也是坑,flask_principal的作者已经不更新了,上次提交代码是5年前。。。。。。。Flask-Security这个也是一个大坑,你的user必须要有email,active 字段,而且你还必须用WTF,局限太大了所以放弃。 如果想要了解这里找到一篇不错的Blog传送门||同上。
我的项目结构树
CMDB/
├─app/
│ ├─auth/
│ ├─main/
│ ├─static/
│ ├─templates/
│ ├─__init__.py
│ ├─config.conf
│ ├─models.py
├─manager.py
models.py,必要的模型关系user和role是多对多
from . import db,login_manager
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash,check_password_hash#转换密码用到的库
from flask_login import UserMixin
#角色<-->用户,关联表
roles_users = db.Table(
'role_user',
db.Column('user_id',db.Integer(),db.ForeignKey('user.id')),
db.Column('role_id',db.Integer(),db.ForeignKey('role.id'))
)
#角色表
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer(),primary_key=True)
name = db.Column(db.String(80),unique=True)
description = db.Column(db.String(255))
def __repr__(self):
return "" .format(self.id)
#用户表
class User(db.Model,UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer(),primary_key=True)
username = db.Column(db.String(80),unique=True,nullable=False)
password_hash = db.Column(db.String(255))
#多对多关联
roles = db.relationship('Role',secondary='role_user',backref=db.backref('users',lazy='dynamic'))
def __repr__(self):
return "" .format(self.id)
# 这个方法是用于用户登录后返回数据库的ID到session中用来登录
@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
@property
def password(self):
raise AttributeError("密码不允许读取,请使用check_password_hash()进行验证密码")
#转换密码为hash存入数据库
@password.setter
def password(self,password):
self.password_hash = generate_password_hash(password)
#检查密码
def check_password_hash(self, password):
return check_password_hash(self.password_hash,password)
__init__.py,初始化
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager,current_user
#权限认证需要的包
from flask_principal import Principal, Permission, RoleNeed, identity_loaded
# 初始化对象
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.session_protection = 'strong' # 让session功能更加强壮
login_manager.login_view = 'auth.login' # 制定系统默认的登录页面
login_manager.login_message = "请登录后再进行访问该页面!"
#初始化Principal
principals = Principal()
#添加admin权限
admin_permission = Permission(RoleNeed('admin'))
# 工厂化
def create_app():
app = Flask(__name__)
app.config.from_pyfile('config.conf')
db.init_app(app)
login_manager.init_app(app)
principals.init_app(app)
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
#设置当前用户身份为login登录对象
identity.user = current_user
#添加UserNeed到identity user对象
if hasattr(current_user, 'id'):
identity.provides.add(UserNeed(current_user.id))
#每个Role添加到identity user对象,roles是User的多对多关联
if hasattr(current_user, 'roles'):
for role in current_user.roles:
identity.provides.add(RoleNeed(role.name))
# 注册蓝图
from .main import main as main_blueprint
from .auth import auth as auth_blueprint
app.register_blueprint(main_blueprint)
app.register_blueprint(auth_blueprint)
return app
1.identity_loaded :信号实现函数,需要访问 app 对象,所以在 create_app里面构造
2.identity_changed:用户身份变化时发送。
(其实有点复杂我也不是太理解,大家也可以看下其它大神的,我这里就当是一个demo好拉)
auth-views.py,login,logout都需要改变身份。
from . import auth
from .. import db,admin_permission
from ..models import User
from flask import redirect,render_template,request,url_for,flash
from flask_login import login_user,logout_user,current_user,login_required
# from flask_security import roles_required,current_user,logout_user,login_user,login_required
from flask_principal import Identity, AnonymousIdentity, identity_changed, current_app,IdentityContext
#上下文处理,可以在jinja2判断是否有执行权限
@auth.app_context_processor
def context():
admin= IdentityContext(admin_permission)
return dict(admin=admin)
#登录
@auth.route('/login',methods=['GET','POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
check = request.form.get('check')
user = User.query.filter_by(username=username).first()
if user and user.check_password_hash(password):
if check:
login_user(user,remember=True)
else:
login_user(user)
#登录时的身份变化
identity_changed.send(
current_app._get_current_object(),
identity=Identity(user.id))
return redirect(url_for('main.index'))
else:
flash("账户或密码错误!")
return render_template('login.html')
#注销
@auth.route('/logout',methods=['GET','POST'])
def logout():
logout_user()
#注销后身份变成匿名
identity_changed.send(
current_app._get_current_object(),
identity=AnonymousIdentity())
return redirect(url_for('auth.login'))
需要权限保护的路由
#增加用户
@auth.route('/adduser',methods=['GET','POST'])
@login_required
@admin_permission.require(http_exception=403)
def adduser():
if request.method == 'POST':
username = request.form['user']
password = request.form['password']
search = User.query.filter_by(username=username).first()
if search is None:
user = User(username=username,password=password)
db.session.add(user)
db.session.commit()
flash('应该是提交成功了吧。')
else:
flash('这个用户名应该是有人用了吧。。')
return render_template('adduser.html')
前端鉴权,有权限才显示
{% if admin.can() %}
<li><a href="{{ url_for('auth.adduser') }}"><i class="fa fa-circle-o">i>增加用户a>li>
{% endif %}