最近在做系统权限这一块,第一次发文记录分享一下,不足之处还请多多指教
现在我们做的Web系统,基本都需要权限管理,一是方便用户管理:不同用户展示不同的功能菜单;二是方便我们维护:有些菜单是系统管理员操作的.
权限基础数据表
- 用户表
- 角色表
- 用户角色表
- 菜单表
- 角色菜单表
权限校验分为两块
- 前端校验
- 后端校验
前端权限校验
前端校验只是为了提升用户体验,并不能真正的拦截请求
流程
- 成功登陆系统后,服务端会返回一个
session/token,然后存储在前端 - 用户获取系统菜单:菜单列表、菜单对应的按钮,同样存储到
storage中 - 根据权限加载页面菜单 & 根据一定规则删除掉没有权限的按钮
菜单基本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完成权限的管理,这里做基本代码展示
我们获取到的菜单是树形结构,这里我把所有的菜单权限编码抽取出来放在一个集合里面,这样校验的时候取值就很容易了
这里使用Redis的set数据结构存储,避免权限编码重复
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,前端再根据状态码去做相应处理就好了.
如果有更好的处理方式,还请教我一下,谢谢