.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,再者业务需求较简单,如有不对之处,欢迎大家指出,必定虚心求教。

时间: 2024-10-11 08:34:01

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

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

.NET WebAPI 用ActionFilterAttribute实现token令牌验证与对Action的权限控制 项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了. 最近刚做完权限这一块,分享出来给大家.欢迎各种吐槽批判践踏... 先说说用户身份的识别,简单的做了一个token机制.用户登录,后台产生令牌,发放令牌,用户携带令牌访问... 1.cache管理类,由于博主使用的HttpR

webapi框架搭建-安全机制(三)-基于角色的权限控制

webapi框架搭建系列博客 上一篇已经完成了"身份验证",如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使用方法 配置Authorize 比较简单,直接上代码 using System.Collections.Generic; using System.Net.Http; using System.Security.Claims; using System.Web.Http; using we

3.7 钱包、帐户、token、智能合约、action、权限、权重和阀值之间关系

3.7.1 账户.钱包和密钥的关系 如图所示,右边是EOS Wallet钱包,里面只存放私钥及由该私钥产生的公钥(公私钥对),而且钱包有一个密码,需要输入密码才能解锁钱包,读取私钥.钱包和账户没有直接的关系左边是EOS Account账户,可以把它看成是一个保险箱,里面有EOS Token以及智能合约,而需要转移里面的EOS Token(或者执行智能合约),你需要钱包中对应的私钥来解锁这个保险箱. 创建一个账户的命令是:cleos create account {创建者账户名} {新的账户名}

Net WebApi一个简单的Token验证

1.前言 WebAPI主要开放数据给手机APP,Pad,其他需要得知数据的系统,或者软件应用.Web 用户的身份验证,及页面操作权限验证是B/S系统的基础功能.我上次写的<Asp.Net MVC WebAPI的创建与前台Jquery ajax后台HttpClient调用详解>这种跟明显安全性不是那么好,于是乎这个就来了 ,用户需要访问的API都必须带有票据过来,说白了就是登陆之后含有用户信息的Token.开始撸... 2.新建一个WebApi项目 在App_Start文件夹下面新建一个Base

jwt laravel 使用 jwttoken解析token jwt生成token jwt令牌验证

本文介绍jwt token在laravel中的安装使用,解决token验证的问题 一.查看laravel版本选定适合自己框架的jwt版本,不同版本之间会有意想不到的惊喜(坑) 根据自己 laravel版本不同使用jwt-auth版本建议如下: Laravel version jwt-auth version 4.* 0.3.* 0.4.* 5.* && <5.5 0.5.* 5.* 1.0.* 以上只是参考,本人在laravel5.4也成功使用了1.0.0-rc.3版本下面详细描述下

ASP.NET Core WebApi基于Redis实现Token接口安全认证

一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebService服务中可以通过SoapHead验证机制来实现,那么在ASP.NET Core WebApi中我们应该如何保证我们的接口安全呢?  近年来RESTful API开始风靡,使用HTTP header来传递认证令牌似乎变得理所应当,而单页应用(SPA).前后端分离架构似乎正在促成越来越多的WEB应

dubbo之令牌验证

防止消费者绕过注册中心访问提供者 在注册中心控制权限,以决定要不要下发令牌给消费者 注册中心可灵活改变授权方式,而不需修改或升级提供者 可以全局设置开启令牌验证 <!--随机token令牌,使用UUID生成--> <dubbo:provider token="true" /> <!--固定token令牌,相当于密码--> <dubbo:provider token="123456" /> 也可在服务级别设置: <

表单提交中的重复问题(表单令牌验证)

在日常的表单提交中,如果由于网络或其他原因,很容易出现同一个表单提交多次,此时可以使用表单令牌验证 在提交的表单里,增加多一个隐藏数据,token,该token由后台脚本生成(如:php使用md5(rand(1,99999999))) 然后将生成的值保持至session或文本中,在表单提交中,对该token进行准确性认证,以确定表单提交的唯一性

PHP Token(令牌)设计应用

PHP Token(令牌)设计 设计目标: 避免重复提交数据. 检查来路,是否是外部提交 匹配要执行的动作(如果有多个逻辑在同一个页面实现,比如新增,删除,修改放到一个PHP文件里操作) 这里所说的token是在页面显示的时候,写到FORM的一个隐藏表单项(type=hidden). token不可明文,如果是明文,那就太危险了,所以要采用一定的加密方式.密文要可逆.俺算法很白痴,所以采用了网上一个现成的方法. 如何达到目的: 怎样避免重复提交? 在SESSION里要存一个数组,这个数组存放以经