利用二进制位运算实现权限控制

2019-04-29

思路来自linux权限rwx 741,其实linux的权限用c语言的实现也是按位计算的。
权限我大致分了几类:url、element、data
简单二进制权限标志位如下,注意这里的位置顺序都是有意义的:

    二进制按位授权
     1           1          1        1
   delete       put        post    get/enable/disable

url:就是普通的api接口或页面地址,拥有post delete put get的权限,用二进制位来表示,0代表没有权限,1代表有权限。如0b0101就是有put get权限

element:就是页面元素。比如按钮,二进制0代表禁用或隐藏,1代表启用,只有最后一位数字有效。

data:指数据权限,不同APP系统很难统一,所以目前我仅针对某一特定的表字段值作为过滤条件来做权限筛选。比如只取出表中username为fish的所有数据,这样用户fish在查看某个表格数据的时候就只能看到跟自己想干的数据。二进制0代表禁用,1代表启用,只有最后一位数字有效。

python的二进制用0b开头,如0b1111,转换成十进制int(0b1111)为15,但是注意bin(15)得到的0b1111却是str类型,是不能进行位运算的。
好在int类型是可以直接做位运算的,所以我们先把想要处理的二进制转换成十进制存到数据库,用的时候直接取出十进制数字进行位运算即可。如:int(0b1111) >> 1
python位操作我就不赘述了,我们要知道的是判断二进制某一位的值是0还是1应该使用如下公式:

二进制数字快速判断某位是0还是1
N:待判断的二进制数
B:待判断的位(右往左)
(( N >>( B - 1 )) & 1

如:判断0b1111从右往左数第4位是0还是1(从1开始数,不是从0开始)
( ( int(0b1111) >> ( 4 - 1 ) ) & 1

有了以上思路,随便给我们一个4位的二进制数和权限类型,就可以判断是什么权限了

# -*- coding:utf-8 -*-
class mapPerm:
    @classmethod
    def map_perm(cls,action,type):
        perms = ['get','post','put','delete']
		# 这里故意在列表里倒序,因为我们后面检测到没有权限时要pop出去
        if type == 'url':
            for i in range(4, 0, -1): 
				# 这里巧妙的用range进行逆循环,即第一个i为4
                res = ( action >> (i - 1) ) & 1
				# 每循环一次,往右移1位
                if res is 0:
                    perms.pop(i-1)
					# 没有权限,则直接从权限表里删除
        elif type == 'element':
            res = ( action >> (1 - 1) ) & 1
            if res is 0:
                perms = ["disable"]
        elif type == 'data':
            res = (action >> (1 - 1)) & 1
            if res is 1:
                perms = ["enable"]
        return perms
		
	@classmethod
    def auth_method(cls,method,action):
		# 验证对当前请求方法是否有权限
        flag = {'GET':1,'POST':2,'PUT':3,'DELETE':4}.get(method)
        if flag:
            perm = ( action >> (flag-1) ) & 1
            if perm is 1:
                return True
        return False

改进写法:

# -*- coding:utf-8 -*-
class mapPerm:
    @classmethod
    def map_perm(cls,action,type):
        perms = {"data": ["ENABLE"], "element": ["ENABLE"], "url": ['GET', 'POST', 'PUT', 'DELETE']}.get(type)
        for i in range(len(perms), 0, -1):
            res = (action >> (i - 1)) & 1
            if res is 0:
                perms.pop(i - 1)
        return perms
		
	@classmethod
    def auth_method(cls,method,action):
        flag = {'GET':1,'POST':2,'PUT':3,'DELETE':4}.get(method)
        if flag:
            perm = ( action >> (flag-1) ) & 1
            if perm is 1:
                return True
        return False

验证:

print(int(0b1000)) # 转换结果为8,这里的1没有任何作用,因为element类型只判断最后1位
perms = mapPerm.map_perm(8,'element')
print(perms)
has_perm = mapPerm.auth_method(8,'url')
print(perms)

实例:url请求方法权限控制

权限设计思路:
给用户分配组,给组分配权限,权限对指定资源(如url)分配action(get、post等)

鉴权实现方法:
以flask_restplus为例,通过装饰器调用一个鉴权函数,在路由分发到get等方法前进行鉴权。

鉴权函数逻辑:获取用户当前的请求方法和请求的url,再获取用户的权限,然后判断对指定url是否有权限使用当前请求方法访问。

利用装饰器实现中间件:

from functools import wraps
from flask import request,jsonify,g
from app.models import db,User
from app.utils.map_perm import mapPerm


def auth():
    token = None
    if 'X-Token' in request.headers:
        token = request.headers['X-Token']
    try:
        user = User.verify_auth_token(token)
        g.user = user
    except Exception as e:
        return False
    finally:
        db.session.commit()
    groups = user.groups
    for group in groups:
        if group.app_id is 1:
		# app_id是指APP系统的ID,因为我当时设计的是多APP统一权限管理系统
            permissions = group.permissions
            for perm in permissions:
                if perm.resource.resource_type is 'url' and perm.resource.resource_code.get("url") == request.path:
                    return mapPerm.auth_method(request.method,perm.action)
    return False

def authenticate(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """
        判断用户是否有权限使用当前请求方法访问URL
        1、获取当前请求方法和请求url,获取用户权限,校验是否有权访问
        """
        acct = auth()  # 自定义鉴权方法
        if acct:
            return func(*args, **kwargs)
        return jsonify({"code": 40000, "message": "无权访问"})
    return wrapper

中间件的使用方法:导入刚才写好的中间件,在类中使用method_decorators = [authenticate]即可

from app.utils import authenticate
@ns.route('/pms/apps_selector/',endpoint="apps_selector",methods=['GET'])
class AppSelector(Resource):
    method_decorators = [authenticate]
    def get(self):
        from flask import g
        print(g.user.code)
        apps = Application.query.all()
        apps_selector = []
        try:
            for app in apps:
                apps_selector.append({ "label":app.app_name,"value":app.id })
            code = 20000
        except Exception as e:
            code = 50000
            apps_selector = "获取失败:%s" % str(e)
        finally:
            db.session.close()

        data = {
            'code':code,
            'data':apps_selector
        }
        return gma.dump(data)

5c74f7a04e2a0.png
5c74f94e77f02.png


标题:利用二进制位运算实现权限控制
作者:fish2018
地址:http://www.devopser.org/articles/2019/04/24/1556061973923.html