基於.Net Framework 4.0 Web API開發(4):ASP.NET Web APIs 基於

来源:http://www.cnblogs.com/qingxiaoxiao/archive/2017/04/28/6782153.html
-Advertisement-
Play Games

概述: ASP.NET Web API 的好用使用過的都知道,沒有複雜的配置文件,一個簡單的ApiController加上需要的Action就能工作。但是在使用API的時候總會遇到跨域請求的問題, 特別各種APP萬花齊放的今天,對API使用者身份角色驗證是不能避免的(完全開發的API不需要對使用者身 ...


概述: 

  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     }
View Code

     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     }
View Code

     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     }
View Code

    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 (!anonymousActio

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡的“私闖sys.databases”是指Entity Framework預設發起的查詢:SELECT Count(*) FROM sys.databases WHERE [name]=N'資料庫名' 註:本文針對的是Entity Framework Code First場景,Entity Fra ...
  • 最近做一個指紋採集和比對的功能,因為公司整個項目是WEB類型的,所以指紋採集的模塊要嵌套在網頁中,那隻有用ActiveX了,以下是一些操作及效果,做個筆記! 新建用戶控制項,編寫CS代碼,如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
  • Visual Studio 我們在Windows平臺上開發應用程式使用的工具主要是Visual Studio.這個集成開發環境已經演化了很多年,從一個簡單的C++編輯器和編譯器到一個高度集成、支持軟體開發整個生命周期的多語言環境。 Visual Studio以及它發佈的工具和服務提供了:設計、開發、 ...
  • 在開發Windows服務程式時,我們一般需要添加安裝程式,即:serviceInstaller,裡面有幾個關於名稱屬性,你都搞明白了嗎? 1.Description:表示服務說明(描述服務是乾什麼的); 2.DisplayName:表示友好名稱,可以理解為服務名的別名; 3.ServiceName: ...
  • 從編程的角度來講,ASP.NET Web API針對CORS的實現僅僅涉及到HttpConfiguration的擴展方法EnableCors和EnableCorsAttribute特性。但是整個CORS體系不限於此,在它們背後隱藏著一系列的類型,我們將會利用本章餘下的內容對此作全面講述,今天我們就來 ...
  • 為什麼要使用StringBuilder 為什麼要使用StringBuilder 為什麼要使用StringBuilder 為什麼使用StringBuilder要從string對象的特性說起。 為什麼使用StringBuilder要從string對象的特性說起。 string對象在進行字元串拼接時,因為 ...
  • 簡介 簡介 對於.net來說,用web api來構建服務是一個不錯的選擇,都是http請求,調用簡單,但是如果真的要在程式中調用,則還有些工作要做,比如我們需要手寫httpClient調用,並映射Model, 如果服務少還可以,多了就繁瑣了。 Swagger Swagger 關於Swagger的信息 ...
  • 我們在上面對ASP.NET Core預設提供的具有跨平臺能力的KestrelServer進行了詳細介紹(《聊聊ASP.NET Core預設提供的這個跨平臺的伺服器——KestrelServer》),為了讓讀者朋友們對管道中的Server具有更加深刻的認識,接下來我們採用實例演示的形式創建一個自定義的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...