目录 前言 权限 中间层 小结 前言 在之前已经提到过,公用类库Util已经开源,目的一是为了简化开发的工作量,毕竟有些常规的功能类库重复率还是挺高的,二是为了一起探讨学习软件开发,用的人越多问题也就会越多,解决的问题越多功能也就越完善,仓库地址: April.Util_github,April.Util_gitee,还没关注的朋友希望可以先mark,后续会持续维护。 权限 在之前的net core WebApi——公用库April.Util公开及发布中已经介绍了初次发布的一些功能,其中包括缓存,日志,加密,统一的配置等等,具体可以再回头看下这篇介绍,而在其中有个TokenUtil,因为当时发布的时候这块儿还没有更新上,趁着周末来整理下吧。 关于webapi的权限,可以借助Identity,Jwt,但是我这里没有借助这些,只是自己做了个token的生成已经存储用户主要信息,对于权限我想大多数人已经有了一套自己的权限体系,所以这里我简单介绍下我的思路。 首先对于菜单做权限标示,请求的控制器,请求的事件 菜单信息维护后,设置角色对应多个菜单 管理员对应多个角色 在登录的时候根据账号信息获取对应管理员的角色及最终菜单,控制器,事件 处理管理员信息后自定义token,可设置token过期时间,token可以反解析(如果到期自动重新授权,我这里没有处理) 每次访问接口的时候(除公开不需校验的接口),根据请求的路径判断是否有当前控制器权限(通过中间层),进入接口后判断是否有对应权限(通过标签) 通过上述流程来做权限的校验,当然这里只是针对单应用,如果是多应用的话,这里还要考虑应用问题(如,一个授权认证工程主做身份校验,多个应用工程通用一个管理)。 首先,我们需要一个可以存储管理员的对应属性集合AdminEntity,主要存储基本信息,控制器集合,权限集合,数据集合(也就是企业部门等)。 /// /// 管理员实体 /// public class AdminEntity { private int _ID = -1; private string _UserName = string.Empty; private string _Avator = string.Empty; private List _Controllers = new List(); private List _Permissions = new List(); private int _TokenType = 0; private bool _IsSuperManager = false; private List _Depts = new List(); private int _CurrentDept = -1; private DateTime _ExpireTime = DateTime.Now; /// /// 主键 /// public int ID { get => _ID; set => _ID = value; } /// /// 用户名 /// public string UserName { get => _UserName; set => _UserName = value; } /// /// 头像 /// public string Avator { get => _Avator; set => _Avator = value; } /// /// 控制器集合 /// public List Controllers { get => _Controllers; set => _Controllers = value; } /// /// 权限集合 /// public List Permissions { get => _Permissions; set => _Permissions = value; } /// /// 访问方式 /// public int TokenType { get => _TokenType; set => _TokenType = value; } /// /// 是否为超管 /// public bool IsSuperManager { get => _IsSuperManager; set => _IsSuperManager = value; } /// /// 企业集合 /// public List Depts { get => _Depts; set => _Depts = value; } /// /// 当前企业 /// public int CurrentDept { get => _CurrentDept; set => _CurrentDept = value; } /// /// 过期时间 /// public DateTime ExpireTime { get => _ExpireTime; set => _ExpireTime = value; } } 之后我们来完成TokenUtil这块儿,首先是生成我们的token串,因为考虑到需要反解析,所以这里采用的是字符串加解密,当然这个加密串具体是什么可以自定义,目前我这里设置的是固定需要两个参数{id},{ts},目的是为了保证加密串的唯一,当然也是为了过期无感知重新授权准备的。 public class TokenUtil { /// /// 设置token /// /// 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; } /// /// 通过token获取当前人员信息 /// /// /// 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; } /// /// 通过用户请求信息获取Token信息 /// /// 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; } /// /// 移除Token /// /// public static void RemoveToken(string token = "") { if (string.IsNullOrEmpty(token)) { token = GetTokenByContent(); } if (!string.IsNullOrEmpty(token)) { Remove(token); } } #region 多个登录 /// /// 多个登录设置缓存 /// /// /// public static void SetUserToken(string token, int userid) { Dictionary> dicusers = CacheUtil.Get>>("UserToken"); if (dicusers == null) { dicusers = new Dictionary>(); } List listtokens = new List(); 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); } /// /// 多个登录删除缓存 /// /// public static void RemoveUserToken(int userid) { Dictionary> dicusers = CacheUtil.Get>>("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); } } } /// /// 多个登录获取 /// /// /// public static List GetUserToken(int userid) { Dictionary> dicusers = CacheUtil.Get>>("UserToken"); List lists = new List(); 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(token); break; case AprilEnums.TokenCacheType.Cache: admin = CacheUtil.Get(token); break; case AprilEnums.TokenCacheType.Session: admin = SessionUtil.Get(token); break; case AprilEnums.TokenCacheType.Redis: admin = RedisUtil.Get(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.IsSupe