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

 
分类: Web API

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

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

    项目背景是一个社区类的APP(求轻吐...),博主主要负责后台业务及接口.以前没玩过webAPI,但是领导要求必须用这个(具体原因鬼知道),只好硬着头皮上了. 最近刚做完权限这一块,分享出来给大家.欢 ...

  2. Springboot token令牌验证解决方案 在SpringBoot实现基于Token的用户身份验证

    1.首先了解一下Token 1.token也称作令牌,由uid+time+sign[+固定参数]组成: uid: 用户唯一身份标识 time: 当前时间的时间戳 sign: 签名, 使用 hash/e ...

  3. Nginx集群之SSL证书的WebApi令牌验证

    目录 1       大概思路... 1 2       Nginx集群之SSL证书的WebApi令牌验证... 1 3       Openssl生成SSL证书... 2 4       编写.NE ...

  4. ThinkPHP自动令牌验证(附实例)

    一.数据表结构 user表结构如下: id username password 二.view模板部分 /view/index.html页面如下:   1 2 3 4 5 6 <form acti ...

  5. Taurus.MVC 2.2.3.4 :WebAPI 实现权限控制认证(及功能增强说明)

    前言: 前两天,当我还在老家收拾行旅,准备回广州,为IT连的创业再战365天时, 有网友扣上问:Taurus.MVC中如何实现认证和权限控制,最好能做个小例子. 我一不小心回了句:等回广州我再写篇文章 ...

  6. Asp.Net WebApi一个简单的Token验证

    1.前言: WebAPI主要开放数据给手机APP,Pad,其他需要得知数据的系统,或者软件应用.Web 用户的身份验证,及页面操作权限验证是B/S系统的基础功能.我上次写的<Asp.Net MV ...

  7. 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证

    基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...

  8. token方法可用于临时关闭令牌验证,

    token方法可用于临时关闭令牌验证,例如: $model->token(false)->create(); 即可在提交表单的时候临时关闭令牌验证(即使开启了TOKEN_ON参数). 大理 ...

  9. ASP.NET Core WebApi基于JWT实现接口授权验证

    一.ASP.Net Core WebApi JWT课程前言 我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再 ...

随机推荐

  1. jQuery的理论基础

    概述 jQuery是用JavaScript语言编写的函数库,我们用时,可以直接调用jQuery中相应的函数,对于JavaScript的理解,前面的博客已经介绍过了,在这里只说一下函数的作用,也可以说为 ...

  2. 怎么解决 ubuntu 装kde桌面遇到的汉化问题

    正在读取软件包列表... 完成正在分析软件包的依赖关系树 正在读取状态信息... 完成 现在没有可用的软件包 language-pack-kde-zh,但是它被其它的软件包引用了.这可能意味着这个缺失 ...

  3. hdu 2074 堆放篮 好开心图纸标题

    堆放篮 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  4. C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法。

    原文:C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法. 一般我们在开发Windows Phone App时,需要取得萤幕的大小来自定义最佳化控制项的大小,但是开如何取得萤 ...

  5. jQuery插件使用和写法

    jQuery插件分类3中: 1.封装对象方法的插件. 2.封装全局函数的插件. 3.选择器插件. jQuery插件机制 jQuery提供了两个用于扩展jQuery功能的方法: 1.jQuery.fn. ...

  6. JAVA jdbc(数据库连接池)学习笔记(转)

    学习内容: 1.JDBC的含义... JDBC想必学过JAVA的就不会陌生,JDBC到底是什么呢?其实就是由JAVA的一些类和接口构成的API,保存在java.sql和javax.sql..包中的一些 ...

  7. Android的第二次增加SurfaceView基本使用

    本文来源于http://blog.csdn.net/hellogv/ ,引用必须注明出处. 上次介绍MediaPlayer的时候略微介绍了SurfaceView,SurfaceView因为能够直接从内 ...

  8. log4net使用特定的解释

    说明:该程序演示如何使用log4net记录日志信息. log4net它是-known开源组件的日志记录功能.使用log4net可以很容易地将信息记录到文件.控制台.Windows事件日志和数据库(含有 ...

  9. 移动web:翻页场景动画

    在移动web,特别是在微信中,经常看到一种翻页动画效果,也称为场景动画. 一页一页的翻过,像在看书,每页的内容以各种"炫酷"的效果出现在你的眼里,配上一首动听的音乐,你有没有喜欢上 ...

  10. 升级到tomcat8时Artifact SpringMvcDemo:war exploded: Server is not connected. Deploy is not

    The method getDispatcherType() is undefined for the type HttpServletRequest 升级到tomcat8 http://segmen ...