• .NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制


    项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口。以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了。

     

    最近刚做完权限这一块,分享出来给大家。欢迎各种吐槽批判践踏...

     

    先说说用户身份的识别,简单的做了一个token机制。用户登录,后台产生令牌,发放令牌,用户携带令牌访问...

    1.cache管理类,由于博主使用的HttpRuntime.Cache来存储token,IIS重启或者意外关闭等情况会造成cache清空,只好在数据库做了cache的备份,在cache为空的时候查询数据库是否有cache数据,有则是cache被意外清空,需要重新放在cache中。

        /// <summary>
        /// 缓存管理
        /// 将令牌、用户凭证以及过期时间的关系数据存放于Cache中
        /// </summary>
        public class CacheManager
        {
    private static readonly ITempCacheService tempCacheService = ServiceLocator.Instance.GetService<ITempCacheService>(); /// <summary> /// 初始化缓存数据结构 /// </summary> /// token 令牌 /// uuid 用户ID凭证 /// userType 用户类别 /// timeout 过期时间 /// <remarks> /// </remarks> private static void CacheInit() { if (HttpRuntime.Cache["PASSPORT.TOKEN"] == null) { DataTable dt = new DataTable(); dt.Columns.Add("token", Type.GetType("System.String")); dt.Columns["token"].Unique = true; dt.Columns.Add("uuid", Type.GetType("System.Object")); dt.Columns["uuid"].DefaultValue = null; dt.Columns.Add("userType", Type.GetType("System.String")); dt.Columns["userType"].DefaultValue = null; dt.Columns.Add("timeout", Type.GetType("System.DateTime")); dt.Columns["timeout"].DefaultValue = DateTime.Now.AddDays(7); DataColumn[] keys = new DataColumn[1]; keys[0] = dt.Columns["token"]; dt.PrimaryKey = keys; var tempCaches = tempCacheService.GetAllCaches(); if (tempCaches.Any()) { foreach (var tempCacheDTOShow in tempCaches) { DataRow dr = dt.NewRow(); dr["token"] = tempCacheDTOShow.UserToken; dr["uuid"] = tempCacheDTOShow.UserAccountId; dr["userType"] = tempCacheDTOShow.UserType.ToString(); dr["timeout"] = tempCacheDTOShow.EndTime; dt.Rows.Add(dr); } } //Cache的过期时间为 令牌过期时间*2 HttpRuntime.Cache.Insert("PASSPORT.TOKEN", dt, null, DateTime.MaxValue, TimeSpan.FromDays(7 * 2)); } } /// <summary> /// 获取用户UUID标识 /// </summary> /// <param name="token"></param> /// <returns></returns> public static Guid GetUUID(string token) { CacheInit(); DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow[] dr = dt.Select("token = '" + token + "'"); if (dr.Length > 0) { return new Guid(dr[0]["uuid"].ToString()); } return Guid.Empty; } /// <summary> /// 获取用户类别(分为员工、企业、客服、管理员等,后期做权限验证使用) /// </summary> /// <param name="token"></param> /// <returns></returns> public static string GetUserType(string token) { CacheInit(); DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow[] dr = dt.Select("token = '" + token + "'"); if (dr.Length > 0) { return dr[0]["userType"].ToString(); } return null; } /// <summary> /// 判断令牌是否存在 /// </summary> /// <param name="token">令牌</param> /// <returns></returns> public static bool TokenIsExist(string token) { CacheInit(); DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow[] dr = dt.Select("token = '" + token + "'"); if (dr.Length > 0) { var timeout = DateTime.Parse(dr[0]["timeout"].ToString()); if (timeout > DateTime.Now) { return true; } else { RemoveToken(token); return false; } } return false; } /// <summary> /// 移除某令牌 /// </summary> /// <param name="token"></param> /// <returns></returns> public static bool RemoveToken(string token) { CacheInit(); DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow[] dr = dt.Select("token = '" + token + "'"); if (dr.Length > 0) { dt.Rows.Remove(dr[0]); } return true; } /// <summary> /// 更新令牌过期时间 /// </summary> /// <param name="token">令牌</param> /// <param name="time">过期时间</param> public static void TokenTimeUpdate(string token, DateTime time) { CacheInit(); DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow[] dr = dt.Select("token = '" + token + "'"); if (dr.Length > 0) { dr[0]["timeout"] = time; } } /// <summary> /// 添加令牌 /// </summary> /// <param name="token">令牌</param> /// <param name="uuid">用户ID凭证</param> /// <param name="userType">用户类别</param> /// <param name="timeout">过期时间</param> public static void TokenInsert(string token, object uuid, string userType, DateTime timeout) { CacheInit(); // token不存在则添加 if (!TokenIsExist(token)) { DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"]; DataRow dr = dt.NewRow(); dr["token"] = token; dr["uuid"] = uuid; dr["userType"] = userType; dr["timeout"] = timeout; dt.Rows.Add(dr); HttpRuntime.Cache["PASSPORT.TOKEN"] = dt; tempCacheService.Add_TempCaches(new List<TempCacheDTO_ADD>() { new TempCacheDTO_ADD() { EndTime = timeout, UserAccountId = new Guid(uuid.ToString()), UserToken = new Guid(token), UserType = (UserType)Enum.Parse(typeof(UserType),userType) } }); } // token存在则更新过期时间 else { TokenTimeUpdate(token, timeout); tempCacheService.Update_TempCaches(new Guid(token), timeout); } } }

    2.接下来就是对用户携带的token进行验证了,通过继承ActionFilterAttribute来实现,在这里还需要考虑到匿名访问API,对于部分API,是允许匿名访问(不登录访问)的。所以,先写一个代表匿名的Attribute:

        /// <summary>
        /// 匿名访问标记
        /// </summary>
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
        public class AnonymousAttribute : Attribute
        {
        }

    然后给允许匿名访问的Action打上[Anonymous]标签就OK,再来看我们的token验证代码:

        /// <summary>
        /// 用户令牌验证/// </summary>
        public class TokenProjectorAttribute : ActionFilterAttribute
        {
            private const string UserToken = "token";
            private readonly IAccountInfoService accountInfoService = ServiceLocator.Instance.GetService<IAccountInfoService>();
            private readonly ITempCacheService tempCacheService = ServiceLocator.Instance.GetService<ITempCacheService>();
    
            public override void OnActionExecuting(HttpActionContext actionContext)
            {
                // 匿名访问验证
                var anonymousAction = actionContext.ActionDescriptor.GetCustomAttributes<AnonymousAttribute>();
                if (!anonymousAction.Any())
                {
                    // 验证token
                    var token = TokenVerification(actionContext);
                }
    
                base.OnActionExecuting(actionContext);
            }
    
            /// <summary>
            /// 身份令牌验证
            /// </summary>
            /// <param name="actionContext"></param>
            protected virtual string TokenVerification(HttpActionContext actionContext)
            {
                // 获取token
                var token = GetToken(actionContext.ActionArguments, actionContext.Request.Method);
    
                // 判断token是否有效
                if (!CacheManager.TokenIsExist(token))
                {
                    throw new UserLoginException("Token已失效,请重新登陆!");
                }
    
                // 判断用户是否被冻结
                if (accountInfoService.Exist_User_IsForzen(AccountHelper.GetUUID(token)))
                {
                    CacheManager.RemoveToken(token);
                    tempCacheService.Delete_OneTempCaches(new Guid(token));
                    throw new UserLoginException("此用户已被冻结,请联系客服!");
                }
    
                return token;
            }
    
            private string GetToken(Dictionary<string, object> actionArguments, HttpMethod type)
            {
                var token = "";
    
                if (type == HttpMethod.Post)
                {
                    foreach (var value in actionArguments.Values)
                    {
                        token = value.GetType().GetProperty(UserToken) == null
                            ? GetToken(actionArguments, HttpMethod.Get)
                            : value.GetType().GetProperty(UserToken).GetValue(value).ToString();
                    }
                }
                else if (type == HttpMethod.Get)
                {
                    if (!actionArguments.ContainsKey(UserToken))
                    {
                        throw new Exception("未附带token!");
                    }
    
                    if (actionArguments[UserToken] != null)
                    {
                        token = actionArguments[UserToken].ToString();
                    }
                    else
                    {
                        throw new Exception("token不能为空!");
                    }
                }
                else
                {
                    throw new Exception("暂未开放其它访问方式!");
                }
    
                return token;
            }
        }

    这里对GetToken方法做一下解释:

    1.博主只做了POST与GET方法的验证,其他请求未使用也就没做,欢迎大家补充

    2.POST方式里面的回调是解决POST请求接口只有一个简单参数的情况,例如下面的接口:

            /// <summary>
            /// 手动向新用户推送短信
            /// </summary>
            /// <returns></returns>
            [HttpPost]
            [Route("api/Common/PushNewUserSMS")]public PushNewWorkSMSResult PushNewUserSMS([FromBody]string token)
            {
                sendMessagesService.PushNewUserSMS();
                return new PushNewWorkSMSResult() { Code = 0 };
            }

    当然,POST方式一般都会把参数写进一个类里,对于一个参数的情况,博主不喜欢那么干。这么写,需要AJAX提交时空变量名才能获取:

        // 推送新用户营销短信
        function pushNewUserSMS() {
            $(".tuiguang").unbind("click");
    // 注意下面的参数为空“” $.post(Config.Api.Common.PushNewUserSMS, {
    "": $.cookie("MPCBtoken") }, function (data) { if (data.Code == 0) { alert("发送成功!"); _initDatas(); } else { Config.Method.JudgeCode(data, 1); } }); }

    这样,我们就只需要在每个controller上打上[TokenProjector]标签,再在允许匿名的Action上打上[Anonymous]标签就能轻松的搞定token验证了。

    3.除了token验证外呢,我门还想对Action进行用户角色的控制,比如一个获取登录用户钱包余额的Action(A),肯定只有员工、企业才能访问,管理员、客服没有钱包,所以不允许访问,从业务上应该是去访问另外一个获取指定用户钱包余额的Action(B),当然这个Action又不能对员工、企业开放权限。这就涉及到需要实现一个控制Action访问权限的功能,上面我们对用户的token进行了验证,那么拿到token就拿到了用户基本信息(包括角色),那就只需要做一个对Action的权限标注就能解决问题了,我们先写一个代表权限控制的Attribute:

        /// <summary>
        /// 权限控制标记
        /// </summary>
        [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
        public class ModuleAuthorizationAttribute : Attribute
        {
            public ModuleAuthorizationAttribute(params string[] authorization)
            {
                this.Authorizations = authorization;
            }
    
            /// <summary>
            /// 允许访问角色
            /// </summary>
            public string[] Authorizations { get; set; }
        }

    在每个需要权限控制的Action上打上[ModuleAuthorization]标签,并注明访问角色:

            /// <summary>
            /// 获取钱包余额
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            [HttpGet]
            [Route("api/Account/GetWalletBalance")]
            [ModuleAuthorization(new[] { "Staff", "Enterprise" })]
            public GetWalletBalanceResult GetWalletBalance(string token)
            {
                var result = this.walletService.Get_Wallet_Balance(AccountHelper.GetUUID(token));
                return new GetWalletBalanceResult()
                {
                    Code = 0,
                    Balance = result
                };
            }
    
            /// <summary>
            /// 管理员
            /// 处理提现申请
            /// </summary>
            /// <param name="handleTempWithdrawalsModel"></param>
            /// <returns></returns>
            [HttpPost]
            [Route("api/Account/HandleTempWithdrawals")]
            [ModuleAuthorization(new[] { "PlatformCustomer" })]
            public HandleTempWithdrawalsResult HandleTempWithdrawals(
                [FromBody] HandleTempWithdrawalsModel handleTempWithdrawalsModel)
            {
                walletService.Handle_TempWithdrawals(AccountHelper.GetUUID(handleTempWithdrawalsModel.token),
                    handleTempWithdrawalsModel.message, handleTempWithdrawalsModel.tempID,
                    handleTempWithdrawalsModel.isSuccess);
                return new HandleTempWithdrawalsResult() { Code = 0 };
            }

    然后我们修改TokenProjectorAttribute这个类,在验证token后做权限验证,权限验证方法如下:

            /// <summary>
            /// Action 访问权限验证
            /// </summary>
            /// <param name="token">身份令牌</param>
            /// <param name="actionContext"></param>
            /// <returns></returns>
            protected virtual void AuthorizeCore(string token, HttpActionContext actionContext)
            {
                // 权限控制Action验证
                var moduleAuthorizationAction = actionContext.ActionDescriptor.GetCustomAttributes<ModuleAuthorizationAttribute>();
                if (moduleAuthorizationAction.Any())
                {
                    var userRole = AccountHelper.GetUserType(token);
                    if (!moduleAuthorizationAction[0].Authorizations.Contains(userRole.ToString()))
                    {
                        throw new Exception("用户非法跨权限访问,token:" + token);
                    }
                }
            }

     

    OK,终于实现了webAPI对用户令牌与Action权限的验证。

     

    当然,博主也是刚接触webAPI,再者业务需求较简单,如有不对之处,欢迎大家指出,必定虚心求教。

  • 相关阅读:
    在Visual Studio 2012 Blue theme下使用Dark theme的文本编辑器颜色设置
    How to build the Robotics Library from source code on Windows
    解数独的小程序
    XCAT在虚拟机上部署系统
    在docker里部署网络服务
    初学python里的yield send next
    opencl初体验
    cuda计算的分块
    尽信书不如无书
    docker on centos
  • 原文地址:https://www.cnblogs.com/csqb-511612371/p/4864650.html
Copyright © 2020-2023  润新知