模型:rbac 基于角色的权限访问控制
什么是权限?
一个包含正则表达式的url就是权限

第一个版本,实现简单的权限验证需求

表结构:

class UserInfo(models.Model):

    name=models.CharField(max_length=32)
    pwd=models.CharField(max_length=32)
    roles=models.ManyToManyField("Role")

    def __str__(self):
        return self.name

class Role(models.Model):
    title=models.CharField(max_length=32)
    permissions=models.ManyToManyField("Permission")

    def __str__(self):
        return self.title

class Permission(models.Model):
    title=models.CharField(verbose_name="权限名称",max_length=32)
    url=models.CharField(max_length=32)

    def __str__(self):
        return self.title

URL

url(r'^login/', views.LoginView.as_view()),
url(r'^userinfo/$', views.UserView.as_view()),
url(r'^userinfo/add', views.adduser),
url(r'^userinfo/(\d+)/change/', views.change),

Login(注册session):

views:

from rbac.models import UserInfo
from  django.views import View

class LoginView(View):

    def get(self,request):
        return render(request,"login.html")

    def post(self,request):

        user=request.POST.get("user")
        pwd=request.POST.get("pwd")
        #查询是否有这个账户
        user=UserInfo.objects.filter(name=user,pwd=pwd).first()
        #如果用户存在
        if user:

            # 将当前登录用户的权限列表注册到session中
            request.session["user_id"]=user.pk

            permission_list = []
            #查询这个用户所有的权限(通过用户-角色-权限),并去重
            permissions=user.roles.all().values("permissions__url").distinct()
            for per in permissions:
                #将这个用户的权限添加到权限列表
                permission_list.append(per.get("permissions__url"))

            print("permission_list:  ",permission_list)

            #将权限列表写入session中
            request.session["permission_list"]=permission_list

        return HttpResponse("OK")

注册中间件

settings

"rbac.apps.RbacConfig"

中间件
rbac.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render,redirect

class PermissionValid(MiddlewareMixin):

    def process_request(self,request):
        #获取当前访问路径
        current_path = request.path  # /userinfo/add/

        #设置白名单
        white_list=["/login/","/admin/*"]
        #如果访问路径在白名单中则放行
        for per in white_list:
             import re
             ret=re.search(per,current_path)
             if ret:
                return None

        #认证用户是否登录
        #从session中获取用户ID
        user_id=request.session.get("user_id")
        #如果用户没有登录则跳转到登录页面
        if not user_id:
            return redirect("/login/")

        #校验权限(第一种方式)
        #从session中获取用户权限列表
        permission_list = request.session["permission_list"]
        import re
        flag = False
        for permission in permission_list:  # "/userinfo/"
            #确定校验规则
            permission = "^%s$" % permission
            ret = re.search(permission, current_path)
            if ret:
                return None

        return HttpResponse("您没有权限访问!")

在使用正则验证权限的时候要注意在确定校验规则时别忘了在路径前后加上^和$

校验包含三个部分:
白名单校验(正则)
登录校验
权限校验

前端页面的校验

前端页面验证当前登录用户是否有登录权限,如果有就显示相应选项

{% if '/userinfo/add/' in permission_list %}
      <a href="/userinfo/add/" class="btn btn-warning addbtn">添加用户</a>
    {% endif %}

    <table class="table table-bordered">
        {% for user in user_list %}
        <tr>
            <td>{{ user.name }}</td>

            {% if "/userinfo/(\d+)/delete/"  in permission_list %}
               <td><a href="/userinfo/{{ user.pk }}/delete/">删除</a></td>
            {% endif %}

            {% if "/userinfo/(\d+)/change/"  in permission_list %}
               <td><a href="/userinfo/{{ user.pk }}/change/">编辑</a></td>
            {% endif %}

        </tr>
        {% endfor %}

    </table>

思考:上面的操作已经实现权限控制的需求,但是判断权限使用的条件是写死的,例如/userinfo/add/,如果能让代码更通用更简洁?


变更数据结构:
添加PermissionGroup表,用于存储权限分组
权限表中添加一个action字段,里面保存add,delete之类的描述行为的信息
权限表中添加一个group字段,与PermissionGroup 表为一对多关系,比如用户表的增删改查权限全规到用户组,角色表的增删改查全归到角色组

第二版

修改后的数据结构

class User(models.Model):
    name=models.CharField(max_length=32)
    pwd=models.CharField(max_length=32)
    roles=models.ManyToManyField(to="Role")

    def __str__(self): return self.name

class Role(models.Model):
    title=models.CharField(max_length=32)
    permissions=models.ManyToManyField(to="Permission")

    def __str__(self): return self.title

class Permission(models.Model):
    title=models.CharField(max_length=32)
    url=models.CharField(max_length=32)

    action=models.CharField(max_length=32,default="")
    group=models.ForeignKey("PermissionGroup",default=1)
    def __str__(self):return self.title

class PermissionGroup(models.Model):
    title = models.CharField(max_length=32)

    def __str__(self): return self.title

url

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/$', views.users),
    url(r'^users/add', views.add_user),
    url(r'^users/delete/(\d+)', views.del_user),
    url(r'^roles/', views.roles),
    url(r'^login/', views.login),
]

注册session

将注册session功能单独提取出来,与rbac模块放到一起
perssions.py

def initial_session(user,request):

    #获取权限url、权限组id、权限action,并去重
    permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct()

    #将获取到的permissions重新组合,变为我们需要的数据格式
    permission_dict={}
    for item in permissions:
        gid=item.get('permissions__group_id')

        if not gid in permission_dict:

            permission_dict[gid]={
                "urls":[item["permissions__url"],],
                "actions":[item["permissions__action"],]
            }
        else:
            permission_dict[gid]["urls"].append(item["permissions__url"])
            permission_dict[gid]["actions"].append(item["permissions__action"])

    #将转换后的数据写到session中
    request.session['permission_dict']=permission_dict

中间件(不要忘了注册到settings.py中)

rbac.py

class ValidPermission(MiddlewareMixin):

    def process_request(self,request):

        #当前访问路径
        current_path = request.path_info

        #检查是否属于白名单
        valid_url_list=["/login/","/reg/","/admin/.*"]

        for valid_url in valid_url_list:
            ret=re.match(valid_url,current_path)
            if ret:
                return None

        #校验是否登录
        user_id=request.session.get("user_id")

        if not user_id:
            return redirect("/login/")

        #校验权限2
        permission_dict=request.session.get("permission_dict")

        for item in permission_dict.values():
              urls=item['urls']
              for reg in urls:
                  reg="^%s$"%reg
                  ret=re.match(reg,current_path)
                  if ret:

                      #如果匹配成功,就给request添加一个actions
                      request.actions=item['actions']
                      return None

        return HttpResponse("没有访问权限!")

视图views

from rbac.models import *
#封装增删改查四种权限的判断语句,模板可以直接调用这个类下的属性即可
class Per(object):
    def __init__(self,actions):
        self.actions=actions
    def add(self):
        return "add" in self.actions
    def delete(self):
        return "delete" in self.actions
    def edit(self):
        return "edit" in self.actions
    def list(self):
        return "list" in self.actions

def users(request):

    user_list=User.objects.all()

    #查询当前登录人得名字
    id=request.session.get("user_id")
    user=User.objects.filter(id=id).first()

    #调用上面的Per类,这样在模板中if per.delete就等同于 if "delete" in self.actions,让代码变得更简洁了
    per = Per(request.actions)

    return render(request,"users.html",locals())

def add_user(request):

    return HttpResponse("add user.....")

def del_user(request,id):

    return HttpResponse("del"+id)

def roles(request):

    role_list=Role.objects.all()
    per = Per(request.actions)
    return render(request,"roles.html",locals())

from rbac.service.perssions import initial_session

def login(request):

    if  request.method=="POST":

        user=request.POST.get("user")
        pwd=request.POST.get("pwd")

        user=User.objects.filter(name=user,pwd=pwd).first()
        if user:
            ############################### 在session中注册用户ID######################
            request.session["user_id"]=user.pk

            ###############################在session注册权限列表##############################
            #查询当前登录用户的所有权限,注册到session中
            initial_session(user,request)

            return HttpResponse("登录成功!")

    return render(request,"login.html")

模板

举例:

{% if per.add %}
<a href="/users/add/" class="btn btn-primary">添加用户</a>
{% endif %}

附录:注册session时的数据转换

在上面initial_session中permissions获取到的数据格式为:

[
     {'permissions__url': '/users/add/', 
     'permissions__group_id': 1, 
     'permissions__action': 'add'}, 

     {'permissions__url': '/roles/', 
     'permissions__group_id': 2, 
     'permissions__action': 'list'}, 

     {'permissions__url': '/users/delete/(\\d+)', 
     'permissions__group_id': 1, 
     'permissions__action': 'delete'}, 

     {'permissions__url': 'users/edit/(\\d+)', 
     'permissions__group_id': 1, 
     'permissions__action': 'edit'}
]

我们需要把它转换为我们想要的格式:

permission_dict={        
 1: {
 'urls': ['/users/', '/users/add/', '/users/delete/(\\d+)', 'users/edit/(\\d+)'], 
 'actions': ['list', 'add', 'delete', 'edit']}, 

 2: {
 'urls': ['/roles/'],
 'actions': ['list']}
 }

方法1:

#将获取到的permissions重新组合,变为我们需要的数据格式
permission_dict={}
for item in permissions:
    gid=item.get('permissions__group_id')

    if not gid in permission_dict:

        permission_dict[gid]={
            "urls":[item["permissions__url"],],
            "actions":[item["permissions__action"],]
        }
    else:
        permission_dict[gid]["urls"].append(item["permissions__url"])
        permission_dict[gid]["actions"].append(item["permissions__action"])