ASP.NET Web APIs 基于令牌TOKEN验证的实现(保存到DB的Token)
http://www.cnblogs.com/niuww/p/5639637.html
保存到DB的Token
基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现
概述:
ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作。但是在使用API的时候总会遇到跨域 请求的问题, 特别各种APP万花齐放的今天,对API使用者身份角色验证是不能避免的(完全开发的API不需要对使用者身份角色进行管控,可以绕过),这篇文章就来谈 谈基于令牌TOKEN身份验证的实现。
问题:
对于Web API的选择性的开放,使用者无论使用AJAX,还是HttpClient对接,总要对使用者的身份角色进行验证,然而使用API总有跨域使用情况的存在,这样就导致所有基于cookie验证方式都不再适用于API的验证。
原因:
比如,基于form表单验证的基础是登录验证成功后,用户的信息存在缓存或数据库或cookie,无论哪种方式存储用户信息,都不能绕过对 cookie的使用,所以form表单验证方法对于禁用cookie的浏览器都不能正常使用,结论就是不能使用cookie 的环境就不能使用基本的form表单验证方式。因此WEB API 由于跨域的使用,导致cookie不能正常工作,所以不能再使用基于表单验证的方式来实现。
基于令牌TOKEN验证方法的实现:
方法一:
1. 实现对缓存TOKEN的管理,以防IIS服务器的宕机,可以对TOKEN进行持久化存储处理,每次IIS重启重新初始化已经登录成功TOKEN缓存。实现如下:

1 public class UserTokenManager
2 {
3 private static readonly IUserTokenRepository _tokenRep;
4 private const string TOKENNAME = "PASSPORT.TOKEN";
5
6 static UserTokenManager()
7 {
8 _tokenRep = ContainerManager.Resolve<IUserTokenRepository>();
9 }
10 /// <summary>
11 /// 初始化缓存
12 /// </summary>
13 private static List<UserToken> InitCache()
14 {
15 if (HttpRuntime.Cache[TOKENNAME] == null)
16 {
17 var tokens = _tokenRep.GetAll();
18 // cache 的过期时间, 令牌过期时间 *2
19 HttpRuntime.Cache.Insert(TOKENNAME, tokens, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromDays(7 * 2));
20 }
21 var ts = (List<UserToken>)HttpRuntime.Cache[TOKENNAME];
22 return ts;
23 }
24
25
26 public static int GetUId(string token)
27 {
28 var tokens = InitCache();
29 var result = 0;
30 if (tokens.Count > 0)
31 {
32 var id = tokens.Where(c => c.Token == token).Select(c => c.UId).FirstOrDefault();
33 if (id != null)
34 result = id.Value;
35 }
36 return result;
37 }
38
39
40 public static string GetPermission(string token)
41 {
42 var tokens = InitCache();
43 if (tokens.Count == 0)
44 return "NoAuthorize";
45 else
46 return tokens.Where(c => c.Token == token).Select(c => c.Permission).FirstOrDefault();
47 }
48
49 public static string GetUserType(string token)
50 {
51 var tokens = InitCache();
52 if (tokens.Count == 0)
53 return "";
54 else
55 return tokens.Where(c => c.Token == token).Select(c => c.UserType).FirstOrDefault();
56 }
57
58 /// <summary>
59 /// 判断令牌是否存在
60 /// </summary>
61 /// <param name="token"></param>
62 /// <returns></returns>
63 public static bool IsExistToken(string token)
64 {
65 var tokens = InitCache();
66 if (tokens.Count == 0) return false;
67 else
68 {
69 var t = tokens.Where(c => c.Token == token).FirstOrDefault();
70 if (t == null)
71 return false;
72 else if (t.Timeout < DateTime.Now)
73 {
74 RemoveToken(t);
75 return false;
76 }
77 else
78 {
79 // 小于8小时 更新过期时间
80 if ((t.Timeout - DateTime.Now).TotalMinutes < 1 * 60 - 1)
81 {
82 t.Timeout = DateTime.Now.AddHours(8);
83 UpdateToken(t);
84 }
85 return true;
86 }
87
88 }
89 }
90
91 /// <summary>
92 /// 添加令牌, 没有则添加,有则更新
93 /// </summary>
94 /// <param name="token"></param>
95 public static void AddToken(UserToken token)
96 {
97 var tokens = InitCache();
98 // 不存在 怎增加
99 if (!IsExistToken(token.Token))
100 {
101 token.ID = 0;
102 tokens.Add(token);
103 // 插入数据库
104 _tokenRep.Add(token);
105 }
106 else // 有则更新
107 {
108 UpdateToken(token);
109 }
110 }
111
112 public static bool UpdateToken(UserToken token)
113 {
114 var tokens = InitCache();
115 if (tokens.Count == 0) return false;
116 else
117 {
118 var t = tokens.Where(c => c.Token == token.Token).FirstOrDefault();
119 if (t == null)
120 return false;
121 t.Timeout = token.Timeout;
122 // 更新数据库
123 var tt = _tokenRep.FindByToken(token.Token);
124 if (tt != null)
125 {
126 tt.UserType = token.UserType;
127 tt.UId = token.UId;
128 tt.Permission = token.Permission;
129 tt.Timeout = token.Timeout;
130 _tokenRep.Update(tt);
131 }
132 return true;
133 }
134 }
135 /// <summary>
136 /// 移除指定令牌
137 /// </summary>
138 /// <param name="token"></param>
139 /// <returns></returns>
140 public static void RemoveToken(UserToken token)
141 {
142 var tokens = InitCache();
143 if (tokens.Count == 0) return;
144 tokens.Remove(token);
145 _tokenRep.Remove(token);
146 }
147
148 public static void RemoveToken(string token)
149 {
150 var tokens = InitCache();
151 if (tokens.Count == 0) return;
152
153 var ts = tokens.Where(c => c.Token == token).ToList();
154 foreach (var t in ts)
155 {
156 tokens.Remove(t);
157 var tt = _tokenRep.FindByToken(t.Token);
158 if (tt != null)
159 _tokenRep.Remove(tt);
160 }
161 }
162
163
164 public static void RemoveToken(int uid)
165 {
166 var tokens = InitCache();
167 if (tokens.Count == 0) return;
168
169 var ts = tokens.Where(c => c.UId == uid).ToList();
170 foreach (var t in ts)
171 {
172 tokens.Remove(t);
173 var tt = _tokenRep.FindByToken(t.Token);
174 if (tt != null)
175 _tokenRep.Remove(tt);
176 }
177 }
178 }

2. 新建ApiAuthorizeAttribute类,继承AuthorizeAttribute,重写方法IsAuthorized,这样基于TOKEN验证方式就完成了。实现如下:

1 public class ApiAuthorizeAttribute : AuthorizeAttribute
2 {
3 protected override bool IsAuthorized(HttpActionContext actionContext)
4 {
5 // 验证token
6 //var token = actionContext.Request.Headers.Authorization;
7 var ts = actionContext.Request.Headers.Where(c => c.Key.ToLower() == "token").FirstOrDefault().Value;
8 if (ts != null && ts.Count() > 0)
9 {
10 var token = ts.First<string>();
11 // 验证token
12 if (!UserTokenManager.IsExistToken(token))
13 {
14 return false;
15 }
16 return true;
17 }
18
19 if (actionContext.Request.Method == HttpMethod.Options)
20 return true;
21 return false;
22 }
23 }

3. 登录实现

1 /// <summary>
2 /// 账户
3 /// </summary>
4 public class AccountController : ApiController
5 {
6 /// <summary>
7 /// 登录
8 /// </summary>
9 /// <param name="user">登录人员信息: 账号,密码 ,是否记住密码</param>
10 /// <returns></returns>
11 [HttpPost]
12 [AllowAnonymous]
13 public ResultData Login([FromBody]LoginUser user)
14 {
15 string mobile = user.Mobile;
16 string password = user.Password;
17 bool IsRememberMe = user.IsRememberMe;
18
19 if (string.IsNullOrEmpty(mobile) || string.IsNullOrEmpty(password))
20 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));
21
22 User u=null;
23 IMembershipService membershipSvc = ContainerManager.Container.Resolve<IMembershipService>();
24 LoginResultEnum loginResult = membershipSvc.Login(mobile, password, out u);
25 if (loginResult == LoginResultEnum.Success)
26 {
27 //SetAuthenticationTicket(u, IsRememberMe);
28
29 // token 处理
30 UserTokenManager.RemoveToken(u.ID);
31 // 生成新Token
32 var token = Utility.MD5Encrypt(string.Format("{0}{1}", Guid.NewGuid().ToString("D"), DateTime.Now.Ticks));
33 // token过期时间
34 int timeout = 8;
35 if (!int.TryParse(ConfigurationManager.AppSettings["TokenTimeout"], out timeout))
36 timeout = 8;
37 // 创建新token
38 var ut = new UserToken()
39 {
40 Token = token,
41 Timeout = DateTime.Now.AddHours(timeout),
42 UId = u.ID,
43 UserType = (u.IsSaler.HasValue && u.IsSaler.Value) ? "Saler" : "Vip"
44 };
45
46 UserTokenManager.AddToken(ut);
47
48
49 // 登录log
50 var logRep = ContainerManager.Container.Resolve<ISysLogRepository>();
51 var log = new Log()
52 {
53 Action = "Login",
54 Detail = "会员登录:" + u.Mobile + "|" + u.Name,
55 CreateDate = DateTime.Now,
56 CreatorLoginName = u.Mobile,
57 IpAddress = GetClientIp(this.Request)
58 };
59
60 logRep.Add(log);
61
62 var data = new
63 {
64 id = u.ID,
65 issaler = u.IsSaler.HasValue ? u.IsSaler.Value : false,
66 mobile = u.Mobile,
67 token = token
68 };
69 var result = new ResultData(data);
70 result.desc = "登录成功";
71 return result;
72 }
73
74 if (loginResult == LoginResultEnum.UserNameUnExists)
75 {
76 return new ResultData(((int)LoginResultEnum.UserNameUnExists), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameUnExists));
77 }
78 if (loginResult == LoginResultEnum.VerifyCodeError)
79 {
80 return new ResultData(((int)LoginResultEnum.VerifyCodeError), EnumExtension.GetEnumDescription(LoginResultEnum.VerifyCodeError));
81 }
82 if (loginResult == LoginResultEnum.UserNameOrPasswordError)
83 {
84 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));
85 }
86 return new ResultData(ResultType.UnknowError, "登录失败,原因未知");
87 }
88 /// <summary>
89 /// 退出当前账号
90 /// </summary>
91 /// <returns></returns>
92 [HttpPost]
93 public ResultData SignOut()
94 {
95 // 登录log
96 var logRep = ContainerManager.Resolve<ISysLogRepository>();
97 var log = new Log()
98 {
99 Action = "SignOut",
100 Detail = "会员退出:" + RISContext.Current.CurrentUserInfo.UserName,
101 CreateDate = DateTime.Now,
102 CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName,
103 IpAddress = GetClientIp(this.Request)
104 };
105 logRep.Add(log);
106 //System.Web.Security.FormsAuthentication.SignOut();
107 UserTokenManager.RemoveToken(this.Token);
108 return new ResultData(ResultType.Success, "退出成功");
109 }
110 }

4. 测试API
这样就可以配合.NET原有的 AllowAnonymousAttribute 属性使用, 使用方法如下:
不需要验证身份的 类或者Action 添加 [AllowAnonymous]属性,否则添加[ApiAuthorize]

1 /// <summary>
2 /// 测试
3 /// </summary>
4 [ApiAuthorize]
5 public class TestController : BaseApiController
6 {
7 /// <summary>
8 /// 测试权限1
9 /// </summary>
10 [HttpGet]
11 public string TestAuthorize1()
12 {
13 return "TestAuthorize1";
14 }
15 /// <summary>
16 /// 测试权限2
17 /// </summary>
18 [AllowAnonymous]
19 [HttpGet]
20 public string TestAuthorize2()
21 {
22 return "TestAuthorize2";
23 }
24 }

测试一:

1 //TestAuthorize
2 function TestAuthorize1() {
3 $.ajax({
4 type: "get",
5 url: host + "/mobileapi/test/TestAuthorize1",
6 dataType: "text",
7 data: {},
8 beforeSend: function (request) {
9 request.setRequestHeader("token", $("#token").val()); // 请求发起前在头部附加token
10 },
11 success: function (data) {
12 alert(data);
13 },
14 error: function (x, y, z) {
15 alert("报错无语");
16 }
17 });
18 }

结果如下:
测试二:

1 //TestAuthorize
2 function TestAuthorize2() {
3 $.ajax({
4 type: "get",
5 url: host + "/mobileapi/test/TestAuthorize2",
6 dataType: "text",
7 data: {},
8 beforeSend: function (request) {
9 request.setRequestHeader("token", $("#token").val()); // 请求发起前在头部附加token
10 },
11 success: function (data) {
12 alert(data);
13 },
14 error: function (x, y, z) {
15 alert("报错无语");
16 }
17 });
18 }

结果如下:
测试三:

1 //TestAuthorize
2 function TestAuthorize1() {
3 $.ajax({
4 type: "get",
5 url: host + "/mobileapi/test/TestAuthorize1",
6 dataType: "text",
7 data: {},
8 beforeSend: function (request) {
9 //request.setRequestHeader("token", $("#token").val()); // 请求发起前在头部附加token
10 },
11 success: function (data) {
12 alert(data);
13 },
14 error: function (x, y, z) {
15 alert("报错无语");
16 }
17 });
18 }

结果如下:
测试四:

1 //TestAuthorize
2 function TestAuthorize2() {
3 $.ajax({
4 type: "get",
5 url: host + "/mobileapi/test/TestAuthorize2",
6 dataType: "text",
7 data: {},
8 beforeSend: function (request) {
9 //request.setRequestHeader("token", $("#token").val()); // 请求发起前在头部附加token
10 },
11 success: function (data) {
12 alert(data);
13 },
14 error: function (x, y, z) {
15 alert("报错无语");
16 }
17 });
18 }

结果如下:
方法二:
此方法缺点就是每次请求都需要附带token请求参数,这对于有强迫症的程序猿来说是一种折磨,不细说,实现代码如下,有需要的自己研究研究:

1 /// <summary>
2 /// 用户令牌验证
3 /// </summary>
4 public class TokenAuthorizeAttribute : ActionFilterAttribute
5 {
6 private const string UserToken = "token";
7 public override void OnActionExecuting(HttpActionContext actionContext)
8 {
9 // 匿名访问验证
10 var anonymousAction = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
11 if (!anonymousAction.Any())
12 {
13 // 验证token
14 var token = TokenVerification(actionContext);
15 }
16 base.OnActionExecuting(actionContext);
17 }
18
19 /// <summary>
20 /// 身份令牌验证
21 /// </summary>
22 /// <param name="actionContext"></param>
23 protected virtual string TokenVerification(HttpActionContext actionContext)
24 {
25 string msg = "";
26 // 获取token
27 var token = GetToken(actionContext, out msg);
28 if (!string.IsNullOrEmpty(msg))
29 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = msg });
30 // 判断token是否有效
31 if (!UserTokenManager.IsExistToken(token))
32 {
33 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
34 //actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.Unauthorized, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
35 // actionContext.Response = actionContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, "Token已失效,请重新登录!");
36 }
37
38 return token;
39 }
40
41 private string GetToken(HttpActionContext actionContext, out string msg)
42 {
43 Dictionary<string, object> actionArguments = actionContext.ActionArguments;
44 HttpMethod type = actionContext.Request.Method;
45 msg = "";
46 var token = "";
47 if (type == HttpMethod.Post)
48 {
49 if (actionArguments.ContainsKey(UserToken))
50 {
51 if (actionArguments[UserToken] != null)
52 token = actionArguments[UserToken].ToString();
53 }
54 else
55 {
56 foreach (var value in actionArguments.Values)
57 {
58 if (value != null && value.GetType().GetProperty(UserToken) != null)
59 token = value.GetType().GetProperty(UserToken).GetValue(value, null).ToString();
60 }
61 }
62
63 if (string.IsNullOrEmpty(token))
64 msg = "登录超时,请重新登录!";
65 }
66 else if (type == HttpMethod.Get)
67 {
68 if (!actionArguments.ContainsKey(UserToken))
69 msg = "还未登录";
70 // throw new HttpException(401, "还未登录");
71
72 if (actionArguments[UserToken] != null)
73 token = actionArguments[UserToken].ToString();
74 else
75 msg = "登录超时,请重新登录!";
76 }
77 else
78 {
79 throw new HttpException(404, "暂未开放除POST,GET之外的访问方式!");
80 }
81 return token;
82 }
83 }
84
85 public class NoAuthData
86 {
87 public string code { get; set; }
88 public string msg { get; set; }
89 }

此篇到此结束,欢迎大家讨论!
« 上一篇:基于.Net Framework 4.0 Web API开发(5):ASP.NET Web APIs AJAX 跨域请求解决办法(CORS实现)
» 下一篇:js Date 时间格式化的扩展
ASP.NET Web APIs 基于令牌TOKEN验证的实现(保存到DB的Token)的更多相关文章
- 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题, ...
- Web APIs 基于令牌TOKEN验证的实现
Web APIs 基于令牌TOKEN验证的实现 概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但 ...
- Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth
Recently I worked with a customer assisting them in implementing their Web APIs using the new ASP.NE ...
- ASP.NET Web API基于OData的增删改查,以及处理实体间关系
本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先是比较典型的一对多关系,Supplier和Product. public class Product { ...
- [转]ASP.NET Web API基于OData的增删改查,以及处理实体间关系
本文转自:http://www.cnblogs.com/darrenji/p/4926334.html 本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先 ...
- 基于.Net Framework 4.0 Web API开发(2):ASP.NET Web APIs 参数传递方式详解
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.调用API过程中参数的传递是必须的,本节就来谈谈 ...
- 基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是项目,总有异常发生,本节就来谈谈API的异常 ...
- 基于.Net Framework 4.0 Web API开发(5):ASP.NET Web APIs AJAX 跨域请求解决办法(CORS实现)
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题,特 ...
- ASP.NET Web API 2 之参数验证
Ø 前言 目前 C# 比较流行使用 ASP.NET Web API 来承载 Web 接口,提供与客户端之间的数据交互,现在的版本已经是 2.0 了.既然是接口就少不了对输入参数的验证,所以本文主要探 ...
随机推荐
- 【Think in java 读书笔记】多态
在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征.
- 机器学习:Kullback-Leibler Divergence (KL 散度)
今天,我们介绍机器学习里非常常用的一个概念,KL 散度,这是一个用来衡量两个概率分布的相似性的一个度量指标.我们知道,现实世界里的任何观察都可以看成表示成信息和数据,一般来说,我们无法获取数据的总体, ...
- java ip number to string
package com.awkj; import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownH ...
- canvas默认是黑色全透明,不是白色全透明。
- caffe 中solver.prototxt
关于cifar-10和mnist的weight_decay和momentum也是相当的重要:就是出现一次把cifar-10的两个值直接用在mnist上,发现错误很大.
- HTTP基本原理(转)
1. HTTP简介 HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议.它可以使浏览器更加高效,使网络传输减少. ...
- java一些必会算法
经典算法的Java实现 (1)河内塔问题: 42 (2)费式数列 43 (3)巴斯卡(Pascal)三角形 44 (4)蒙地卡罗法求 PI 45 (5)最大公因数.最小公倍数 46 (6)阿姆斯壮数 ...
- Java当中的JVM
Java当中JVM 01 在使用控制面板时的实质: Hello.java使用javac,然后变成为Hello.class通过运行java这个命令,在类加载器中(含有加载,验证,准备,解析,初始化,使用 ...
- inner join 与一般笛卡尔积的区别
inner join 与一般笛卡尔积的区别:inner join是笛卡尔积的特殊形式.如果有表a和表b,表a有m条记录,表b有n条记录,则一般笛卡尔积后得到的记录条数是m*n条,记录之间的组合是随意的 ...
- Spring mvc:配置不拦截指定的路径
<!-- 访问拦截 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**/ ...