当前位置: 移动技术网 > IT编程>开发语言>.net > net core WebApi——April.Util更新之权限

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

2019年11月11日  | 移动技术网IT编程  | 我要评论

抚仙湖恋歌歌词,官声小说,马伽术特殊摔投法

目录

前言

在之前已经提到过,公用类库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来完成登录授权这块儿,至于数据的划分,毕竟这个东西不是通用的,所以只是点出来而没有去整合,如果有好的建议或者自己整合的通用类库也可以跟我交流。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网