ASP.NET Web APIs 基於令牌TOKEN驗證的實現(保存到DB的Token)

http://www.cnblogs.com/niuww/p/5639637.htmlhtml

 

保存到DB的Tokengit

 

基於.Net Framework 4.0 Web API開發(4):ASP.NET Web APIs 基於令牌TOKEN驗證的實現

概述: 

  ASP.NET Web API 的好用使用過的都知道,沒有複雜的配置文件,一個簡單的ApiController加上須要的Action就能工做。可是在使用API的時候總會遇到跨域 請求的問題, 特別各類APP萬花齊放的今天,對API使用者身份角色驗證是不能避免的(徹底開發的API不須要對使用者身份角色進行管控,能夠繞過),這篇文章就來談 談基於令牌TOKEN身份驗證的實現。ajax

問題:

   對於Web API的選擇性的開放,使用者不管使用AJAX,仍是HttpClient對接,總要對使用者的身份角色進行驗證,然而使用API總有跨域使用狀況的存在,這樣就致使全部基於cookie驗證方式都再也不適用於API的驗證。數據庫

緣由:

  好比,基於form表單驗證的基礎是登陸驗證成功後,用戶的信息存在緩存或數據庫或cookie,不管哪一種方式存儲用戶信息,都不能繞過對 cookie的使用,因此form表單驗證方法對於禁用cookie的瀏覽器都不能正常使用,結論就是不能使用cookie 的環境就不能使用基本的form表單驗證方式。所以WEB API 因爲跨域的使用,致使cookie不能正常工做,因此不能再使用基於表單驗證的方式來實現。api

基於令牌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 }
複製代碼


此篇到此結束,歡迎你們討論!

 

  

 

那些曾覺得念念不忘的事情就在咱們念念不忘的過程當中,被咱們遺忘了。
標籤: ASP.NET, Web API, AJAX
好文要頂 關注我 收藏該文
20
1
 

« 上一篇:基於.Net Framework 4.0 Web API開發(5):ASP.NET Web APIs AJAX 跨域請求解決辦法(CORS實現)
» 下一篇:js Date 時間格式化的擴展

相關文章
相關標籤/搜索