其实我在使用的是Cyclone,构建于Twisted之上,但是API却是使用Tornado的。考虑到国内Python Weber少,Tornado Weber相对多一些,所以做一次标题党。
借鉴Flask经验
无论Cyclone/Tornado都只实现了auth类。但是业务本身的RBAC需要用户自己搞。我参考了O'Reilly Flask Web Development作者Grinberg的做法。
但是就RBAC的逻辑来说,不嫌麻烦的话,可以在每个Controller入口处判断一下。
简陋而繁琐
给大家看看我的代码(没有code标签,所以代码格式破了):
python
class DoctorHandler(BaseHandler): @cyclone.web.authenticated @storage.DatabaseSafe @defer.inlineCallbacks def get(self): user = yield storage.users.find_first( where=("user_email=%s", self.current_user["email"])) if not user: self.clear_current_user() self.redirect("/") defer.returnValue(None) role = yield storage.roles.find_first( where=("id=%s", user.user_role)) if not role: self.clear_current_user() self.redirect("/") defer.returnValue(None) if role.name not in "Doctor": m = "Unauthorized Access" raise cyclone.web.HTTPError("403", m) m = "Hello, %s, You can do %s"%(role.name, role.permission) self.render("dashboard.html", role=m)
Flask 代码中的修饰符
我预计该项目应该有100个URL Controller入口,所以最好以decorator形式减少冗余代码。我们看看Grinberg的代码。
python
class User(UserMixin, db.Model): def can(self, permission): return self.role is not None and (self.role.permissions & permissions) == permissions def is_admin(self): return self.can(Permission.ADMINISTRATER)
......
decorator.py
python
class permission_required(permission) def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not current_user.can(permission): abort(403) return f(*args, **kwargs) return decorated_function return decorator def admin_required(f): return permission_required(Permision.ADMINISTRATER)(f)
所以,我原来的代码可以写成:
python
Class SomeControllerHandler(BaseHandler): @cyclone.web.authenticated @storage.DatabaseSafe @defer.inlineCallbacks @permission_required(Permission.MODERATE_COMMENTS) def get(): print "Only moderator is allowed" @admin_required print "only admin is allowed"
Flask与Cylone/Torndao的不同点
理想状态下,所有RBAC判断应该以decorator形式存在。但是Tornado/Cyclone是异步框架,而且decorator本身就是需要异步访问数据库。而异步访问本身就是decorator,也就是说需要用decorator来修改decorator。比如storage.DatabaseSafe,defer.inlineCallbacks...
......增加了调试难度。
其实,Flask在app/models中user模型是完备的。而Tornado/Cyclone演示程序中的app/storage中user类居然是空白的。所有的动作居然都是在Views.py即控制器中去做的。所以说,Tornado/Cyclone并没有严格按照MVC的模型在做。所以代码移植起来有难度。
在Flask的decorator中需要访问user.can方法,而Tornado/Cyclone的current_user居然是保存在Cookie中的。两者完全不同。
目标
我的目标就是修正Cyclone/Tornado的用户App的设计,将decorator引入到RBAC的设计中。否则,太繁琐,难以维护了。
希望有过这方面经验的人给一些建议。