net core WebApi——April.Util更新之权限

目录

  • 前言
  • 权限
  • 中间层
  • 小结

前言

在之前已经提到过,公用类库Util已经开源,目的一是为了简化开发的工作量,毕竟有些常规的功能类库重复率还是挺高的,二是为了一起探讨学习软件开发,用的人越多问题也就会越多,解决的问题越多功能也就越完善,仓库地址: April.Util_githubApril.Util_gitee,还没关注的朋友希望可以先mark,后续会持续维护。

权限

在之前的net core WebApi——公用库April.Util公开及发布中已经介绍了初次发布的一些功能,其中包括缓存,日志,加密,统一的配置等等,具体可以再回头看下这篇介绍,而在其中有个TokenUtil,因为当时发布的时候这块儿还没有更新上,趁着周末来整理下吧。

关于webapi的权限,可以借助Identity,Jwt,但是我这里没有借助这些,只是自己做了个token的生成已经存储用户主要信息,对于权限我想大多数人已经有了一套自己的权限体系,所以这里我简单介绍下我的思路。

  1. 首先对于菜单做权限标示,请求的控制器,请求的事件
  2. 菜单信息维护后,设置角色对应多个菜单
  3. 管理员对应多个角色
  4. 在登录的时候根据账号信息获取对应管理员的角色及最终菜单,控制器,事件
  5. 处理管理员信息后自定义token,可设置token过期时间,token可以反解析(如果到期自动重新授权,我这里没有处理)
  6. 每次访问接口的时候(除公开不需校验的接口),根据请求的路径判断是否有当前控制器权限(通过中间层),进入接口后判断是否有对应权限(通过标签)

通过上述流程来做权限的校验,当然这里只是针对单应用,如果是多应用的话,这里还要考虑应用问题(如,一个授权认证工程主做身份校验,多个应用工程通用一个管理)。

首先,我们需要一个可以存储管理员的对应属性集合AdminEntity,主要存储基本信息,控制器集合,权限集合,数据集合(也就是企业部门等)。

    /// <summary>
    /// 管理员实体
    /// </summary>
    public class AdminEntity
    {
        private int _ID = -1;
        private string _UserName = string.Empty;
        private string _Avator = string.Empty;
        private List<string> _Controllers = new List<string>();
        private List<string> _Permissions = new List<string>();
        private int _TokenType = 0;
        private bool _IsSuperManager = false;
        private List<int> _Depts = new List<int>();
        private int _CurrentDept = -1;
        private DateTime _ExpireTime = DateTime.Now;

        /// <summary>
        /// 主键
        /// </summary>
        public int ID { get => _ID; set => _ID = value; }
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get => _UserName; set => _UserName = value; }
        /// <summary>
        /// 头像
        /// </summary>
        public string Avator { get => _Avator; set => _Avator = value; }
        /// <summary>
        /// 控制器集合
        /// </summary>
        public List<string> Controllers { get => _Controllers; set => _Controllers = value; }
        /// <summary>
        /// 权限集合
        /// </summary>
        public List<string> Permissions { get => _Permissions; set => _Permissions = value; }
        /// <summary>
        /// 访问方式
        /// </summary>
        public int TokenType { get => _TokenType; set => _TokenType = value; }
        /// <summary>
        /// 是否为超管
        /// </summary>
        public bool IsSuperManager { get => _IsSuperManager; set => _IsSuperManager = value; }
        /// <summary>
        /// 企业集合
        /// </summary>
        public List<int> Depts { get => _Depts; set => _Depts = value; }
        /// <summary>
        /// 当前企业
        /// </summary>
        public int CurrentDept { get => _CurrentDept; set => _CurrentDept = value; }
        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime ExpireTime { get => _ExpireTime; set => _ExpireTime = value; }
    }

之后我们来完成TokenUtil这块儿,首先是生成我们的token串,因为考虑到需要反解析,所以这里采用的是字符串加解密,当然这个加密串具体是什么可以自定义,目前我这里设置的是固定需要两个参数{id},{ts},目的是为了保证加密串的唯一,当然也是为了过期无感知重新授权准备的。

    public class TokenUtil
    {
        /// <summary>
        /// 设置token
        /// </summary>
        /// <returns></returns>
        public static string GetToken(AdminEntity user, out string expiretimstamp)
        {
            string id = user.ID.ToString();
            double exp = 0;
            switch ((AprilEnums.TokenType)user.TokenType)
            {
                case AprilEnums.TokenType.Web:
                    exp = AprilConfig.WebExpire;
                    break;
                case AprilEnums.TokenType.App:
                    exp = AprilConfig.AppExpire;
                    break;
                case AprilEnums.TokenType.MiniProgram:
                    exp = AprilConfig.MiniProgramExpire;
                    break;
                case AprilEnums.TokenType.Other:
                    exp = AprilConfig.OtherExpire;
                    break;
            }
            DateTime date = DateTime.Now.AddHours(exp);
            user.ExpireTime = date;
            double timestamp = DateUtil.ConvertToUnixTimestamp(date);
            expiretimstamp = timestamp.ToString();
            string token = AprilConfig.TokenSecretFormat.Replace("{id}", id).Replace("{ts}", expiretimstamp);
            token = EncryptUtil.EncryptDES(token, EncryptUtil.SecurityKey);
            //LogUtil.Debug($"用户{id}获取token:{token}");
            Add(token, user);
            //处理多点登录
            SetUserToken(token, user.ID);
            return token;
        }

        /// <summary>
        /// 通过token获取当前人员信息
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public static AdminEntity GetUserByToken(string token = "")
        {
            if (string.IsNullOrEmpty(token))
            {
                token = GetTokenByContent();
            }
            if (!string.IsNullOrEmpty(token))
            {

                AdminEntity admin = Get(token);
                if (admin != null)
                {
                    //校验时间
                    if (admin.ExpireTime > DateTime.Now)
                    {
                        if (AprilConfig.AllowSliding)
                        {
                            //延长时间
                            admin.ExpireTime = DateTime.Now.AddMinutes(30);
                            //更新
                            Add(token, admin);
                        }
                        return admin;
                    }
                    else
                    {
                        //已经过期的就不再延长了,当然后续根据情况改进吧
                        return null;
                    }
                }
            }
            return null;
        }
        /// <summary>
        /// 通过用户请求信息获取Token信息
        /// </summary>
        /// <returns></returns>
        public static string GetTokenByContent()
        {
            string token = "";
            //判断header
            var headers = AprilConfig.HttpCurrent.Request.Headers;
            if (headers.ContainsKey("token"))
            {
                token = headers["token"].ToString();
            }
            if (string.IsNullOrEmpty(token))
            {
                token = CookieUtil.GetString("token");
            }
            if (string.IsNullOrEmpty(token))
            {
                AprilConfig.HttpCurrent.Request.Query.TryGetValue("token", out StringValues temptoken);
                if (temptoken != StringValues.Empty)
                {
                    token = temptoken.ToString();
                }
            }
            return token;
        }
        /// <summary>
        /// 移除Token
        /// </summary>
        /// <param name="token"></param>
        public static void RemoveToken(string token = "")
        {
            if (string.IsNullOrEmpty(token))
            {
                token = GetTokenByContent();
            }
            if (!string.IsNullOrEmpty(token))
            {
                Remove(token);
            }
        }

        #region 多个登录
        /// <summary>
        /// 多个登录设置缓存
        /// </summary>
        /// <param name="token"></param>
        /// <param name="userid"></param>
        public static void SetUserToken(string token, int userid)
        {
            Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
            if (dicusers == null)
            {
                dicusers = new Dictionary<int, List<string>>();
            }
            List<string> listtokens = new List<string>();
            if (dicusers.ContainsKey(userid))
            {
                listtokens = dicusers[userid];
                if (listtokens.Count <= 0)
                {
                    listtokens.Add(token);
                }
                else
                {
                    if (!AprilConfig.AllowMuiltiLogin)
                    {
                        foreach (var item in listtokens)
                        {
                            RemoveToken(item);
                        }
                        listtokens.Add(token);
                    }
                    else
                    {
                        bool isAdd = true;
                        foreach (var item in listtokens)
                        {
                            if (item == token)
                            {
                                isAdd = false;
                            }
                        }
                        if (isAdd)
                        {
                            listtokens.Add(token);
                        }
                    }
                }
            }
            else
            {

                listtokens.Add(token);
                dicusers.Add(userid, listtokens);
            }
            CacheUtil.Add("UserToken", dicusers, new TimeSpan(6, 0, 0), true);
        }
        /// <summary>
        /// 多个登录删除缓存
        /// </summary>
        /// <param name="userid"></param>
        public static void RemoveUserToken(int userid)
        {
            Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
            if (dicusers != null && dicusers.Count > 0)
            {
                if (dicusers.ContainsKey(userid))
                {
                    //删除所有token
                    var listtokens = dicusers[userid];
                    foreach (var token in listtokens)
                    {
                        RemoveToken(token);
                    }
                    dicusers.Remove(userid);
                }
            }
        }
        /// <summary>
        /// 多个登录获取
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static List<string> GetUserToken(int userid)
        {
            Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
            List<string> lists = new List<string>();
            if (dicusers != null && dicusers.Count > 0)
            {
                foreach (var item in dicusers)
                {
                    if (item.Key == userid)
                    {
                        lists = dicusers[userid];
                        break;
                    }
                }
            }
            return lists;
        }
        #endregion

        #region 私有方法(这块儿还需要改进)

        private static void Add(string token,AdminEntity admin)
        {
            switch (AprilConfig.TokenCacheType)
            {
                //不推荐Cookie
                case AprilEnums.TokenCacheType.Cookie:
                    CookieUtil.Add(token, admin);
                    break;
                case AprilEnums.TokenCacheType.Cache:
                    CacheUtil.Add(token, admin, new TimeSpan(0, 30, 0));
                    break;
                case AprilEnums.TokenCacheType.Session:
                    SessionUtil.Add(token, admin);
                    break;
                case AprilEnums.TokenCacheType.Redis:
                    RedisUtil.Add(token, admin);
                    break;
            }
        }

        private static AdminEntity Get(string token)
        {
            AdminEntity admin = null;
            switch (AprilConfig.TokenCacheType)
            {
                case AprilEnums.TokenCacheType.Cookie:
                    admin = CookieUtil.Get<AdminEntity>(token);
                    break;
                case AprilEnums.TokenCacheType.Cache:
                    admin = CacheUtil.Get<AdminEntity>(token);
                    break;
                case AprilEnums.TokenCacheType.Session:
                    admin = SessionUtil.Get<AdminEntity>(token);
                    break;
                case AprilEnums.TokenCacheType.Redis:
                    admin = RedisUtil.Get<AdminEntity>(token);
                    break;
            }
            return admin;
        }

        private static void Remove(string token)
        {
            switch (AprilConfig.TokenCacheType)
            {
                case AprilEnums.TokenCacheType.Cookie:
                    CookieUtil.Remove(token);
                    break;
                case AprilEnums.TokenCacheType.Cache:
                    CacheUtil.Remove(token);
                    break;
                case AprilEnums.TokenCacheType.Session:
                    SessionUtil.Remove(token);
                    break;
                case AprilEnums.TokenCacheType.Redis:
                    RedisUtil.Remove(token);
                    break;
            }
        }
        #endregion
    }

中间层

当然这也在之前已经提到过net core Webapi基础工程搭建(七)——小试AOP及常规测试_Part 1,当时还觉得这个叫做拦截器,too young too simple,至于使用方法这里就不多说了,可以参考之前2.2版本的东西,也可以看代码仓库中的示例工程。

    public class AprilAuthorizationMiddleware
    {
        private readonly RequestDelegate next;

        public AprilAuthorizationMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public Task Invoke(HttpContext context)
        {
            if (context.Request.Method != "OPTIONS")
            {
                string path = context.Request.Path.Value;
                if (!AprilConfig.AllowUrl.Contains(path))
                {
                    //获取管理员信息
                    AdminEntity admin = TokenUtil.GetUserByToken();
                    if (admin == null)
                    {
                        //重新登录
                        return ResponseUtil.HandleResponse(-2, "未登录");
                    }
                    if (!admin.IsSuperManager)
                    {
                        //格式统一为/api/Controller/Action,兼容多级如/api/Controller1/ConrolerInnerName/xxx/Action
                        string[] strValues = System.Text.RegularExpressions.Regex.Split(path, "/");

                        string controller = "";
                        bool isStartApi = false;
                        if (path.StartsWith("/api"))
                        {
                            isStartApi = true;
                        }
                        for (int i = 0; i < strValues.Length; i++)
                        {
                            //为空,为api,或者最后一个
                            if (string.IsNullOrEmpty(strValues[i]) || i == strValues.Length - 1)
                            {
                                continue;
                            }
                            if (isStartApi && strValues[i] == "api")
                            {
                                continue;
                            }
                            if (!string.IsNullOrEmpty(controller))
                            {
                                controller += "/";
                            }
                            controller += strValues[i];
                        }
                        if (string.IsNullOrEmpty(controller))
                        {
                            controller = strValues[strValues.Length - 1];
                        }
                        if (!admin.Controllers.Contains(controller.ToLower()))
                        {
                            //无权访问
                            return ResponseUtil.HandleResponse(401, "无权访问");
                        }

                    }
                }
            }
            return next.Invoke(context);
        }
    }

Ok,我们先来看下Login中的操作以及实现效果吧。

        [HttpPost]
        public async Task<ResponseDataEntity> Login(LoginFormEntity formEntity)
        {
            if (string.IsNullOrEmpty(formEntity.LoginName) || string.IsNullOrEmpty(formEntity.Password))
            {
                return ResponseUtil.Fail("请输入账号密码");
            }
            if (formEntity.LoginName == "admin")
            {
                //这里实际应该通过db获取管理员
                string password = EncryptUtil.MD5Encrypt(formEntity.Password, AprilConfig.SecurityKey);
                if (password == "B092956160CB0018")
                {
                    //获取管理员相关权限,同样是db获取,这里只做展示
                    AdminEntity admin = new AdminEntity
                    {
                        UserName = "超级管理员",
                        Avator = "",
                        IsSuperManager = true,
                        TokenType = (int)AprilEnums.TokenType.Web
                    };
                    string token = TokenUtil.GetToken(admin, out string expiretimestamp);
                    int expiretime = 0;
                    int.TryParse(expiretimestamp, out expiretime);
                    //可以考虑记录登录日志等其他信息
                    return ResponseUtil.Success("", new { username = admin.UserName, avator = admin.Avator, token = token, expire = expiretime });
                }
            }
            else if (formEntity.LoginName == "test")
            {
                //这里做权限演示
                AdminEntity admin = new AdminEntity
                {
                    UserName = "测试",
                    Avator = "",
                    TokenType = (int)AprilEnums.TokenType.Web
                };
                admin.Controllers.Add("weatherforecast");
                admin.Permissions.Add("weatherforecast_log");//控制器_事件(Add,Update...)
                string token = TokenUtil.GetToken(admin, out string expiretimestamp);
                int expiretime = 0;
                int.TryParse(expiretimestamp, out expiretime);
                //可以考虑记录登录日志等其他信息
                return ResponseUtil.Success("", new { username = admin.UserName, avator = admin.Avator, token = token, expire = expiretime });
            }
            //这里其实已经可以考虑验证码相关了,但是这是示例工程,后续可持续关注我,会有基础工程(带权限)的实例公开
            return ResponseUtil.Fail("账号密码错误");
        }

可能乍一看会先吐槽下,明明是异步接口还用同步的方法,没有异步的实现空浪费内存xxx,因为db考虑是要搞异步,所以这里示例就这样先写了,主要是领会精神,咳咳。

来试下效果吧,首先我们随便访问个白名单外的接口。

然后我们通过账号登陆Login接口(直接写死了,admin,123456),获取到token。

然后我们来访问接口。

是不是还是未登录,没错,因为没有token的传值,当然我这里是通过query传值,支持header,token,query。

这里因为是超管,所以权限随意搞,无所谓,接下来展示下普通用户的权限标示。

目前可以通过标签AprilPermission,把当前的控制器与对应事件的权限作为参数传递,之后根据当前管理员信息做校验。

    public class AprilPermissionAttribute : Attribute, IActionFilter
    {

        public string Permission;
        public string Controller;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="_controller">控制器</param>
        /// <param name="_permission">接口事件</param>
        public AprilPermissionAttribute(string _controller, string _permission)
        {
            Permission = _permission;
            Controller = _controller;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            LogUtil.Debug("AprilPermission OnActionExecuted");
        }
        public void OnActionExecuting(ActionExecutingContext context)
        {
            AdminEntity admin = TokenUtil.GetUserByToken();
            if (admin == null || admin.ExpireTime <= DateTime.Now)
            {
                context.Result = new ObjectResult(new { msg = "未登录", code = -2 });
            }
            if (!admin.IsSuperManager)
            {
                string controller_permission = $"{Controller}_{Permission}";
                if (!admin.Controllers.Contains(Controller) || !admin.Permissions.Contains(controller_permission))
                {
                    context.Result = new ObjectResult(new { msg = "无权访问", code = 401 });
                }
            }
        }
    }

针对几个接口做了调整,附上标签后判断权限,我们来测试下登录test,密码随意。




至此权限相关的功能也统一起来,当然如果有个性化的还是需要调整的,后续也是会不断的更新改动。

小结

权限还是稍微麻烦点儿啊,通过中间层,标签以及TokenUtil来完成登录授权这块儿,至于数据的划分,毕竟这个东西不是通用的,所以只是点出来而没有去整合,如果有好的建议或者自己整合的通用类库也可以跟我交流。

原文地址:https://www.cnblogs.com/AprilBlank/p/11832324.html

时间: 2024-10-04 21:43:37

net core WebApi——April.Util更新之权限的相关文章

net core WebApi——缓存神器Redis

目录 前言 Redis 使用 RedisUtil 测试 小结 @ 前言 中秋过完不知不觉都已经快两周没动这个工程了,最近业务需要总算开始搞后台云服务了,果断直接net core搞起,在做的中间遇到了不少问题,这个后续会一点点列出来包括解决方法,今天就先把之前挖的坑填一个. Redis 之前在缓存那篇提到过,Cookie,Session,Cache这几个不同的缓存方式,Cookie是存在客户端浏览器的,Session实质上也是客户端的存储,至于Cache是服务端的,但是如果是分布式的话,这几个方式

net core WebApi——定时任务Quartz

原文:net core WebApi--定时任务Quartz 目录 前言 Quartz 测试 问题及解决方法 小结 前言 本来打算昨天都开始写这篇,就因为要把小团队的博客整理汇总,一看二哈的博客那么多,一个个复制粘贴肯定麻烦(其实是我自己觉得复制麻烦),所以穿插着写了个小爬虫,后续写差不多了就拿出来晾晾吧(py菜鸡水平). 之前开发的时候,忽略了记录,等到想写点儿啥跟后台有关的东西的时候,还得一点点回忆,最近是因为同事给我说,"哎,每个月把数据给我统计下做个界面展示啊".一想到每个月我

AngularJS 2调用.net core WebAPI的几个坑

前几天,按照AngularJS2的英雄指南教程走了一遍,教程网址是http://origin.angular.live/docs/ts/latest/tutorial/. 在步骤完成后,又更进一步,在英雄增删改的时候,直接调用.net core的WebApi来实现后台数据的操作,替换教程中的模拟WebApi方式.在替换.net core WebApi时,还是遇到了一些坑的,这里记录一下. 先来看一下WebApi和AngularJS的源代码: WebApi 1 [Route("api/[contr

Asp.net core WebApi 使用Swagger生成帮助页实例

最近我们团队一直进行.net core的转型,web开发向着前后端分离的技术架构演进,我们后台主要是采用了asp.net core webapi来进行开发,开始每次调试以及与前端人员的沟通上都存在这效率低下的问题,一次在看微软asp.net core官方文档的时候,发现了swagger这个好东西.然后在实际的项目中引入了该技术.我们开发人员测试自己写的api的过程大大得到了简化,前端人员也可以根据我们提供的swagger help pages 自己进行一些前端代码的测试,大大提高了前后端的开发效

Asp.net core WebApi 使用Swagger生成帮助页

最近我们团队一直进行.net core的转型,web开发向着前后端分离的技术架构演进,我们后台主要是采用了asp.net core webapi来进行开发,开始每次调试以及与前端人员的沟通上都存在这效率低下的问题,一次在看微软asp.net core官方文档的时候,发现了swagger这个好东西.然后在实际的项目中引入了该技术.我们开发人员测试自己写的api的过程大大得到了简化,前端人员也可以根据我们提供的swagger help pages 自己进行一些前端代码的测试,大大提高了前后端的开发效

asp.net core webapi 使用ef 对mysql进行增删改查,并生成Docker镜像构建容器运行

1.构建运行mysql容器,添加数据库user 参考Docker创建运行多个mysql容器,地址 http://www.cnblogs.com/heyangyi/p/9288402.html 添加user数据库,添加tbusers表 2.创建asp.net core webapi 应用程序 参考Docker 为 ASP.NET Core WebApi 应用程序生成 Docker 映像,创建容器并运行,地址 http://www.cnblogs.com/heyangyi/p/9323407.htm

ASP.Net Core WebApi几种版本控制对比

原文:ASP.Net Core WebApi几种版本控制对比 一.版本控制的好处: (1)有助于及时推出功能, 而不会破坏现有系统. (2)它还可以帮助为选定的客户提供额外的功能. API 版本控制可以采用不同的方式进行控制,方法如下: (1)在 URL 中追加版本或作为查询字符串参数, (2)通过自定义标头和通过接受标头 在这篇文章中, 让我们来看看如何支持多个版本的 ASP.NET  Core  Web API. 一.创建asp.net core webapi 项目,引用NuGet包:Ins

ASP.NET Core WebApi AspNetCoreRateLimit 限流中间件学习

原文:ASP.NET Core WebApi AspNetCoreRateLimit 限流中间件学习 AspNetCoreRateLimit介绍: AspNetCoreRateLimit是ASP.NET核心速率限制框架,能够对WebApi,Mvc中控制限流,AspNetCoreRateLimit包包含IpRateLimit中间件和ClientRateLimit中间件,每个中间件都可以为不同的场景设置多个限,该框架的作者是stefanprodan,项目nuget地址是https://github.

Asp.net Core WebApi 使用Swagger做帮助文档,并且自定义Swagger的UI

WebApi写好之后,在线帮助文档以及能够在线调试的工具是专业化的表现,而Swagger毫无疑问是做Docs的最佳工具,自动生成每个Controller的接口说明,自动将参数解析成json,并且能够在线调试. 那么要讲Swagger应用到Asp.net Core中需要哪些步骤,填多少坑呢? 安装Swagger到项目 { "dependencies": { "Swashbuckle": "6.0.0-beta902", ........ 或者直接通