Flask Restful API權(quán)限管理設(shè)計(jì)與實(shí)現(xiàn)

在使用flask設(shè)計(jì)restful api的時(shí)候,有一個(gè)很重要的問題就是如何進(jìn)行權(quán)限管理乘盼,以及如何進(jìn)行角色的定義兔跌,在網(wǎng)上找了一下沒有發(fā)現(xiàn)有類似的資料,雖然有些針對(duì)網(wǎng)站進(jìn)行的權(quán)限管理設(shè)計(jì)婉商,但是跟restful api接口的權(quán)限管理還是有很多不同的,于是乎自己動(dòng)手渣叛,豐衣足食丈秩。為方便后來者,特撰此文淳衙!

權(quán)限的設(shè)計(jì)

從本質(zhì)上思考蘑秽,我需要為每個(gè)API接口設(shè)定相應(yīng)的權(quán)限,所以針對(duì)API的權(quán)限列表跟普通網(wǎng)站的權(quán)限設(shè)計(jì)是不同的箫攀,普通網(wǎng)站的權(quán)限設(shè)計(jì)是針對(duì)某個(gè)功能肠牲,比如是否可以comment功能,通常的權(quán)限定義如下:

class Permission:
    """
    權(quán)限表
    """
    COMMENT = 0x01  # 評(píng)論
    MODERATE_COMMENT = 0x02  # 移除評(píng)論

但是針對(duì)restful api靴跛,我們更希望權(quán)限是針對(duì)我們的api接口缀雳,而restful api接口是跟我們路由的endpoint以及http method相關(guān)的,所以我們的權(quán)限設(shè)計(jì)應(yīng)該是類似如下示例中的樣子:

# 這里comments是路由的endpoint梢睛,接口在判斷用戶是否有權(quán)限的時(shí)候
# 可以先獲取到endpoint和http method肥印,然后就可以查看其是否有權(quán)限
comment_permission = {"comments": {"post": True, "get": True, "delete": False}}

角色的設(shè)計(jì)

通常,我們在做網(wǎng)站的角色設(shè)計(jì)時(shí)會(huì)將角色存儲(chǔ)在數(shù)據(jù)庫當(dāng)中绝葡,并會(huì)通過或運(yùn)算(|)賦予角色以特定權(quán)限深碱,如下:

class Role(db.Model):
    """
    用戶角色
    """
    id = db.Column(db.Integer, primary_key=True)
    # 該用戶角色名稱
    name = db.Column(db.String(164))
    # 該用戶角色是否為默認(rèn)
    default = db.Column(db.Boolean, default=False, index=True)
    # 該用戶角色對(duì)應(yīng)的權(quán)限
    permissions = db.Column(db.Integer)
    # 該用戶角色和用戶的關(guān)系
    # 角色為該用戶角色的所有用戶
    users = db.relationship('User', backref='role', lazy='dynamic')

    @staticmethod
    def insert_roles():
        """
        創(chuàng)建用戶角色
        """
        roles = {
            # 定義了兩個(gè)用戶角色(User, Admin)
            'User': (Permission.COMMENT, True),
            'Admin': (Permission.COMMENT |
                      Permission.MODERATE_COMMENT, False)
        }
        for r in roles:
            role = Role.query.filter_by(name=r).first()
            if role is None:
                # 如果用戶角色沒有創(chuàng)建: 創(chuàng)建用戶角色
                role = Role(name=r)
            role.permissions = roles[r][0]
            role.default = roles[r][1]
            db.session.add(role)
            db.session.commit()

這里其實(shí)我一直沒有搞明白,為什么要將角色存儲(chǔ)于數(shù)據(jù)庫當(dāng)中藏畅,在我看來這只會(huì)導(dǎo)致更多的I/O操作從而影響系統(tǒng)的性能敷硅,因此我在設(shè)計(jì)角色的時(shí)候根本沒有考慮存儲(chǔ)到數(shù)據(jù)庫中,角色的數(shù)據(jù)結(jié)構(gòu)在系統(tǒng)運(yùn)行時(shí),直接存在內(nèi)存當(dāng)中竞膳,這樣在接口調(diào)用時(shí)航瞭,可以直接使用角色相關(guān)的數(shù)據(jù)結(jié)構(gòu)。而且由于我們的權(quán)限設(shè)計(jì)也不太相同坦辟,所以我針對(duì)restful api設(shè)計(jì)的Role如下:

USER = 1
ADMIN = 2
VISITOR = 3

Role = {
    USER: {
        "comment": {"post": True, "patch": True, "get": True, "delete": True},
        "share": {"post": True}
    },
    ADMIN: {
        "comment": {"post": True, "patch": True, "get": True, "delete": True},
        "share": {"post": True}
    },
    VISITOR: {
        "comment": {"get": True},
        "share": {"post": True}
    }
}

用戶可以被賦予特定的role刊侯,如下:

userA = {"name": "John", "role": USER}

那么接口如何判斷用戶是否有權(quán)限訪問呢?
首先用戶訪問接口時(shí)都會(huì)帶有用戶信息锉走,restful api一般是通過token來表明身份滨彻,系統(tǒng)通過token來獲取用戶的信息,比如用戶名挪蹭,然后我們可以通過用戶名來獲取用戶的角色role亭饵,假設(shè)我們訪問的接口是comments endpoint的post接口,那么就可以如下判斷:

def access_control(user):
    """判斷用戶是否有訪問權(quán)限梁厉,有就返回True辜羊,沒有返回False"""
    
    # 首先要獲取到API的endpoint和http method,此處代碼省略
    ...
    
    role = user.get('role', VISITOR)
    try:
        if not Role[role][endpoint][http_method]:
            return False
        return True
    except KeyError:
        return False

由于基本所有的接口都需要access control词顾,那么我們把上邊的代碼稍作改變八秃,讓它成為一個(gè)decorator,同時(shí)肉盹,user信息也可以直接獲取而不需要從參數(shù)傳遞昔驱,如下:

from functools import wraps

def get_role():
    # 這里get_resource_by_name用于從數(shù)據(jù)庫中獲取該用戶的信息,這個(gè)需要自己去定義
    # 另外我們可以在登錄驗(yàn)證的時(shí)候或者token驗(yàn)證的時(shí)候講user name存儲(chǔ)于全局變量g中上忍,這樣我們可以隨時(shí)獲取該用戶名
    user = UserModel.get_resource_by_name(g.user_name)
    return user.get("role", VISITOR)

def access_control(func):
    @wraps(func)
    def wrap_func(*args, **kwargs):
        # 同樣要先獲取到API的endpoint和http method骤肛,此處代碼省略
        ...
        
        try:
            if not Role[role][endpoint][http_method]:
                return make_response(
                    jsonify({'error': 'no permission'}), 403)
            return func(*args, **kwargs)
        except KeyError:
            return make_response(
                jsonify({'error': 'no permission'}), 403)
    return wrap_func

以下是一個(gè)獲取圖片resource的使用示例

from flask_restful import Resource

class ImageResource(Resource):
    def __init__(self):
        super(ImageResource, self).__init__()

    @token_auth.login_required
    @access_control
    def get(self, resource_id):
        response = resource_get(resource_id)
        return response

這里另外一個(gè)decortor @token_auth.login_required用于token驗(yàn)證,大家可以先不用理會(huì)窍蓝。到這里我們已經(jīng)可以針對(duì)每個(gè)接口自動(dòng)判斷該用戶是否有權(quán)限訪問了腋颠,而所有權(quán)限的變化,都可以通過修改Role中的權(quán)限來進(jìn)行更改吓笙,而不需要更改原來的代碼秕豫,很爽吧,有木有观蓄?
不過,筆者在項(xiàng)目中還遇到了另外一個(gè)問題祠墅,有時(shí)候針對(duì)一個(gè)接口所有的user都應(yīng)該有權(quán)限侮穿,但是針對(duì)特定的resource,只能resource owner可以操作毁嗦,舉個(gè)栗子亲茅,比如我們要?jiǎng)h除某個(gè)評(píng)論,但是只允許發(fā)布評(píng)論的人才有權(quán)限刪除,也就是comment resource的owner才可以使用delete接口刪除克锣,但是我們所有的用戶在Role定義的時(shí)候delete接口都是True茵肃,這個(gè)怎么辦呢?
這就需要我們在access_control檢測完了之后再進(jìn)一步檢測該用戶是否是resource owner袭祟,所以我們就需要進(jìn)一步檢測验残,這里添加一個(gè)decorator如下:

def get_resource_owner():
    """獲取resource的owner"""
    # 自定義,代碼省略
    ...

def owner_permission_required(func):
    @wrap(func)
    def wrap_func(*args, **kwargs):
        if g.user_name == get_resource_owner():
            return func(*args, **kwargs)
        return make_response(
            jsonify({'error': 'no permission'}), 403)
    return wrap_func

使用如下:

from flask_restful import Resource

class CommentResource(Resource):
    def __init__(self):
        super(CommentResource, self).__init__()

    @token_auth.login_required
    @access_control
    @owner_permission_required
    @marshal_with(image_fields)
    def delete(self, resource_id):
        response = resource_delete(resource_id)
        return response

注意:decorator的順序是不能改變的巾乳。

至此您没,Restful API權(quán)限管理相關(guān)的設(shè)計(jì)就完成了,如果文章給你帶來了啟發(fā)胆绊,記得點(diǎn)贊哦氨鹏!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市压状,隨后出現(xiàn)的幾起案子仆抵,更是在濱河造成了極大的恐慌,老刑警劉巖种冬,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镣丑,死亡現(xiàn)場離奇詭異,居然都是意外死亡碌廓,警方通過查閱死者的電腦和手機(jī)传轰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谷婆,“玉大人慨蛙,你說我怎么就攤上這事〖涂妫” “怎么了期贫?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長异袄。 經(jīng)常有香客問我通砍,道長,這世上最難降的妖魔是什么烤蜕? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任封孙,我火速辦了婚禮,結(jié)果婚禮上讽营,老公的妹妹穿的比我還像新娘虎忌。我一直安慰自己,他們只是感情好橱鹏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布膜蠢。 她就那樣靜靜地躺著堪藐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挑围。 梳的紋絲不亂的頭發(fā)上礁竞,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音杉辙,去河邊找鬼模捂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奏瞬,可吹牛的內(nèi)容都是我干的枫绅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼硼端,長吁一口氣:“原來是場噩夢啊……” “哼并淋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起珍昨,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤县耽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后镣典,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兔毙,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年兄春,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澎剥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赶舆,死狀恐怖哑姚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芜茵,我是刑警寧澤叙量,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站九串,受9級(jí)特大地震影響绞佩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猪钮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一品山、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烤低,春花似錦肘交、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐蚜,卻和暖如春魄懂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闯第。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工市栗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咳短。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓填帽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咙好。 傳聞我的和親對(duì)象是個(gè)殘疾皇子篡腌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 前面兩篇內(nèi)容(RESTful Web Service 架構(gòu)剖析和HTTP Methods 和 RESTful Se...
    JeffreyLi閱讀 15,765評(píng)論 12 191
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)勾效,斷路器嘹悼,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 本文首載于 Gevin的博客 基于一些不錯(cuò)的RESTful開發(fā)組件,可以快速的開發(fā)出不錯(cuò)的RESTful API层宫,...
    Gevin閱讀 11,917評(píng)論 6 111
  • 最近要考試杨伙,得去認(rèn)真的準(zhǔn)備一下了,所以畫畫可能會(huì)慢下來萌腿,但是還是會(huì)畫下去的限匣,不會(huì)放棄的(???)╯╰(???)?
    一只好coffee閱讀 100評(píng)論 0 0