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)有類似的資料,雖然有些針對網(wǎng)站進(jìn)行的權(quán)限管理設(shè)計(jì),但是跟restful api接口的權(quán)限管理還是有很多不同的,于是乎自己動手,豐衣足食。為方便后來者,特撰此文!

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

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

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

但是針對restful api,我們更希望權(quán)限是針對我們的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í)會將角色存儲在數(shù)據(jù)庫當(dāng)中,并會通過或運(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)
    # 該用戶角色對應(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í)我一直沒有搞明白,為什么要將角色存儲于數(shù)據(jù)庫當(dāng)中,在我看來這只會導(dǎo)致更多的I/O操作從而影響系統(tǒng)的性能,因此我在設(shè)計(jì)角色的時(shí)候根本沒有考慮存儲到數(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ì)也不太相同,所以我針對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í)都會帶有用戶信息,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存儲于全局變量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)證,大家可以先不用理會。到這里我們已經(jīng)可以針對每個(gè)接口自動判斷該用戶是否有權(quán)限訪問了,而所有權(quán)限的變化,都可以通過修改Role中的權(quán)限來進(jìn)行更改,而不需要更改原來的代碼,很爽吧,有木有?
不過,筆者在項(xiàng)目中還遇到了另外一個(gè)問題,有時(shí)候針對一個(gè)接口所有的user都應(yīng)該有權(quán)限,但是針對特定的resource,只能resource owner可以操作,舉個(gè)栗子,比如我們要刪除某個(gè)評論,但是只允許發(fā)布評論的人才有權(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容