让你的系统更安全——权限校验

3,107 阅读3分钟

最近在做系统权限这一块,第一次发文记录分享一下,不足之处还请多多指教

现在我们做的Web系统,基本都需要权限管理,一是方便用户管理:不同用户展示不同的功能菜单;二是方便我们维护:有些菜单是系统管理员操作的.

权限基础数据表

  1. 用户表
  2. 角色表
  3. 用户角色表
  4. 菜单表
  5. 角色菜单表

权限校验分为两块

  1. 前端校验
  2. 后端校验

前端权限校验

前端校验只是为了提升用户体验,并不能真正的拦截请求

流程
  1. 成功登陆系统后,服务端会返回一个session/token,然后存储在前端
  2. 用户获取系统菜单:菜单列表、菜单对应的按钮,同样存储到storage
  3. 根据权限加载页面菜单 & 根据一定规则删除掉没有权限的按钮

菜单基本json结构如下:

{
    name:'权限中心',
    permissionCode:"permission",
    isButton:false,
    children:[
        {
            name:'角色管理',
            permissionCode:'role',
            isButton:false,
            children:[
                {
                    name:'角色添加',
                    permissionCode:'role-add',
                    isButton:true
                },
                ...
            ]
        }
    ]
}
菜单列表加载

直接就可以过滤掉没有权限的菜单列表,较为简单

<ul>
    <li v-for="menu in menus">
        <a @click="addPage(menu)">{{menu.name}}</a>
    </li>
</ul>
按钮权限

首先确定好页面元素的属性规则

如给需要权限校验的元素添加一个自定义属性permission,属性值赋予规定的权限编码

//角色页面
<button @click="addRole" permission="role-add">添加</button>

删除掉没有权限的元素(按钮、Tab...)

permission.js

//1.获取到当前页面权限的元素集合  permissionElements
//2.进行权限检查,将没有权限的元素直接给移除掉
function checkPermission(){
    $("[permission]").each(function(ele){
        var permissionCode = ele.getAttribute('permission');
        if(!permissionElements.first("this.permissionCode=="+permissionCode)){
            item.parentNode.removeChild(item);
        }
    })
}
//体验好一点,可以添加一个css,先将页面需要权限校验的元素给隐藏起来
[permission] { display:none; }
//校验完毕后再显示出来
$("[permission]").each...
    .css('display','inline-block')
    
//每次进入页面就调用了一次,进行权限检查

如上jq为主的项目,如果整体使用Vue框架来写会方便许多了

到这里,前端的权限基本完成了,用户可以看到不同的菜单和按钮了。

这里算完成了权限吗,可以提高系统安全性吗

普通用户虽然看不到角色菜单列表了,但是在浏览器地址栏输入/role/index.html 还是一样进入了角色页面,还是可以看到数据,进行各种操作

真正的做到安全,必须得服务端校验,前面提到了前端的权限校验仅仅是为了提升用户体验

后端权限校验

后端使用.net core & redis完成权限的管理,这里做基本代码展示

我们获取到的菜单是树形结构,这里我把所有的菜单权限编码抽取出来放在一个集合里面,这样校验的时候取值就很容易了

这里使用Redisset数据结构存储,避免权限编码重复

var menuList = GetMenus();
string permissionKey = $"user:{CurrentUser.Id}:permissions";
RedisHelper.SAdd(permissionKey,menuList.Select(m => m.PermissionCode).ToArray());
自定义权限特性
public class PermissionAttribute : Attribute
{
    public string PermissionCode { get; set; }
    public PermissionAttribute(string permissionCode)
    {
        PermissionCode = permissionCode;
    }
}
在需要权限校验的Api上添加特性
[HttpGet]
[Route("role/add")]
[Permission("role-add")]
public ResponseBase AddRole(){
    ...
}
添加公共的拦截器Filter,验证所有请求
public class MyAuthFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        ...其他校验
        
        #region 权限校验
        var isCheckPermission = controllerActionDescriptor.MethodInfo.GetCustomAttributes(true)
                   .Any(a => a.GetType().Equals(typeof(PermissionAttribute)));
        if (isCheckPermission)
        {
            var permissionAttribute = controllerActionDescriptor.MethodInfo.CustomAttributes
                .FirstOrDefault(c => c.AttributeType == typeof(PermissionAttribute));
            if (permissionAttribute != null)
            {
                string permissionCode = permissionAttribute.ConstructorArguments[0].Value.ToString();
                string[] codes = RedisHelper.SMembers($"user:{User.Id}:permissions");
                if (!codes.Contains(permissionCode))
                {
                    context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                    //需验证tokne是否过期
                    context.Result = new JsonResult("403没有权限访问资源");
                }
            }
        }
        #endregion
    }
}

到这里基本已经完成啦,权限校验不通过,Http状态码会返回403,前端再根据状态码去做相应处理就好了.

如果有更好的处理方式,还请教我一下,谢谢