【转】WebApi 身份认证解决方案:Basic基础认证
參考路徑:https://www.cnblogs.com/landeanfen/p/5287064.html
?
前言:最近,討論到數據庫安全的問題,于是就引出了WebApi服務沒有加任何驗證的問題。也就是說,任何人只要知道了接口的url,都能夠模擬http請求去訪問我們的服務接口,從而去增刪改查數據庫,這后果想想都恐怖。經過一番折騰,總算是加上了接口的身份認證,在此記錄下,也給需要做身份認證的園友們提供參考。
一、為什么需要身份認證
在前言里面,我們說了,如果沒有啟用身份認證,那么任何匿名用戶只要知道了我們服務的url,就能隨意訪問我們的服務接口,從而訪問或修改數據庫。
1、我們不加身份認證,匿名用戶可以直接通過url隨意訪問接口:
?
可以看到,匿名用戶直接通過url就能訪問我們的數據接口,最終會發生什么事,大家可以隨意暢想。
2、增加了身份認證之后,只有帶了我們訪問票據的請求才能訪問我們的接口。
例如我們直接通過url訪問,會返回401
?
如果是正常流程的請求,帶了票據,就OK了。
可以看到,正常流程的請求,會在請求報文的頭里面增加Authorization這一項,它的值就是我們的Ticket票據信息。
二、Basic基礎認證的原理解析
1、常見的認證方式
我們知道,asp.net的認證機制有很多種。對于WebApi也不例外,常見的認證方式有
- FORM身份驗證
- 集成WINDOWS驗證
- Basic基礎認證
- Digest摘要認證
園子里很多關于WebApi認證的文章,各種認證方式都會涉及到,但感覺都不夠細。這里也并不想去研究哪種驗證方式適用哪種使用場景,因為博主還是覺得“貪多嚼不爛”,也可能是博主能力所限。對于認證機制,弄懂其中一種,其他的都能融會貫通。此篇就使用Basic基礎認證來詳細講解下整個的過程。
1、常見的認證方式
我們知道,asp.net的認證機制有很多種。對于WebApi也不例外,常見的認證方式有
- FORM身份驗證
- 集成WINDOWS驗證
- Basic基礎認證
- Digest摘要認證
園子里很多關于WebApi認證的文章,各種認證方式都會涉及到,但感覺都不夠細。這里也并不想去研究哪種驗證方式適用哪種使用場景,因為博主還是覺得“貪多嚼不爛”,也可能是博主能力所限。對于認證機制,弄懂其中一種,其他的都能融會貫通。此篇就使用Basic基礎認證來詳細講解下整個的過程。
2、Basic基礎認證原理
我們知道,認證的目的在于安全,那么如何能保證安全呢?常用的手段自然是加密。Basic認證也不例外,主要原理就是加密用戶信息,生成票據,每次請求的時候將票據帶過來驗證。這樣說可能有點抽象,我們詳細分解每個步驟:
?這個基本的原理。下面就按照這個原理來看看每一步的代碼如何實現。
三、Basic基礎認證的代碼示例
首先說下我們的示例場景,我們在一個解決方案里面放了兩個項目Web和WebApiCORS,我們這次還是以這個為例來說明。
1、登錄過程
1.1、Web前端
| 1 2 3 4 5 6 7 | <body> ????<div?style="text-align:center;"> ????????<div>用戶名:<input?type="text" id="txt_username" /></div> ????????<div>密? 碼:<input?type="password" id="txt_password"? /></div> ????????<div><input?type="button" value="登錄" id="btn_login" class="btn-default" /></div> ????</div> </body> |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $(function?() { ????$("#btn_login").click(function?() { ????????$.ajax({ ????????????type:?"get", ????????????url:?"http://localhost:27221/api/User/Login", ????????????data: { strUser: $("#txt_username").val(), strPwd: $("#txt_password").val() }, ????????????success:?function?(data, status) { ????????????????if?(status ==?"success") { ????????????????????if?(!data.bRes){ ????????????????????????alert("登錄失敗"); ????????????????????????return; ????????????????????} ????????????????????alert("登錄成功"); ?//登錄成功之后將用戶名和用戶票據帶到主界面 ????????????????????window.location =?"/Home/Index?UserName="?+ data.UserName +?"&Ticket="?+ data.Ticket; ????????????????} ????????????}, ????????????error:?function?(e) { ????????????}, ????????????complete:?function?() { ? ????????????} ? ????????}); ????}); }); |
1.2、登錄的API接口
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public?class?UserController : ApiController ????{ ????????/// <summary> ????????/// 用戶登錄 ????????/// </summary> ????????/// <param name="strUser"></param> ????????/// <param name="strPwd"></param> ????????/// <returns></returns> ????????[HttpGet] ????????public?object?Login(string?strUser,?string?strPwd) ????????{ ????????????if?(!ValidateUser(strUser, strPwd)) ????????????{ ????????????????return?new?{ bRes =?false?}; ????????????} ????????????FormsAuthenticationTicket ticket =?new?FormsAuthenticationTicket(0, strUser, DateTime.Now, ????????????????????????????DateTime.Now.AddHours(1),?true,?string.Format("{0}&{1}", strUser, strPwd), ????????????????????????????FormsAuthentication.FormsCookiePath); ????????????//返回登錄結果、用戶信息、用戶驗證票據信息 ????????????var?oUser =?new?UserInfo { bRes =?true, UserName = strUser, Password = strPwd, Ticket = FormsAuthentication.Encrypt(ticket) }; ????????????//將身份信息保存在session中,驗證當前請求是否是有效請求 ????????????HttpContext.Current.Session[strUser] = oUser; ????????????return?oUser; ????????} ? ????????//校驗用戶名密碼(正式環境中應該是數據庫校驗) ????????private?bool?ValidateUser(string?strUser,?string?strPwd) ????????{ ????????????if?(strUser ==?"admin"?&& strPwd ==?"123456") ????????????{ ????????????????return?true; ????????????} ????????????else ????????????{ ????????????????return?false; ????????????} ????????} ????} ? ????public?class?UserInfo ????{ ????????public?bool?bRes {?get;?set; } ? ????????public?string?UserName {?get;?set; } ? ????????public?string?Password {?get;?set; } ? ????????public?string?Ticket {?get;?set; } ????} |
這里有一點需要注意的是,因為WebApi默認是沒有開啟Session的,所以需要我們作一下配置,手動去啟用session。如何開啟WebApi里面的Session,請參考:http://www.cnblogs.com/tinya/p/4563641.html
正如上面的原理部分說的,登錄如果失敗,則直接返回;如果成功,則將生成的票據Ticket帶到前端,傳到主界面/Home/Index,下面,我們就來看看主界面Home/Index。
2、/Home/Index主界面
| 1 2 3 4 5 6 7 8 9 10 | public?class?HomeController : Controller ????{ ????????// GET: Home ????????public?ActionResult Index(string?UserName,?string?Ticket) ????????{ ????????????ViewBag.UserName = UserName; ????????????ViewBag.Ticket = Ticket; ????????????return?View(); ????????} ????} |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <html> <head> ????<meta?name="viewport" content="width=device-width" /> ????<title>Index</title> ????<script?src="~/Content/jquery-1.9.1.js"></script> ????<link?href="~/Content/bootstrap/css/bootstrap.css" rel="stylesheet" /> ????<script?src="~/Content/bootstrap/js/bootstrap.js"></script> ????<script?src="~/Scripts/Home/Index.js"></script> ????<script?type="text/javascript"> ????????//打開頁面的時候保存票據信息 ????????var UserName = '@ViewBag.UserName'; ????????var Ticket = '@ViewBag.Ticket'; ????</script> </head> <body> ????<div>當前登錄用戶:'@ViewBag.UserName'</div> ? ????<div?id="div_test"> ? ????</div> </body> </html> |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $(function?() { ????$.ajax({ ????????type:?"get", ????????url:?"http://localhost:27221/api/Charging/GetAllChargingData", ????????data: {}, ????????beforeSend:?function?(XHR) { ????????????//發送ajax請求之前向http的head里面加入驗證信息 ????????????XHR.setRequestHeader('Authorization',?'BasicAuth '?+ Ticket); ????????}, ????????success:?function?(data, status) { ????????????if?(status ==?"success") { ????????????????$("#div_test").html(data); ????????????} ????????}, ????????error:?function?(e) { ????????????$("#div_test").html("Error"); ????????}, ????????complete:?function?() { ? ????????} ? ????}); }); |
這里需要說明的是,我們在發送ajax請求之前,通過?XHR.setRequestHeader('Authorization', 'BasicAuth ' + Ticket);?這一句向請求的報文頭里面增加票據信息。就是因為這里加了這一句,所以才有我們下圖中的紅線部分:
?
3、WebApiCORS驗證部分(重點)
我們看到,上面的/Home/Index頁面里面發送了ajax請求去訪問服務的?http://localhost:27221/api/Charging/GetAllChargingData?這個接口,那么我們在WebApi里面怎么去驗證這個請求和合法的請求呢?接下來我們重點看看驗證的這個過程。
3.1、在WebApiCORS項目里面自定義一個類RequestAuthorizeAttribute,去繼承我們的AuthorizeAttribute這個類。然后重寫OnAuthorization方法,在這個方法里面取到請求頭的Ticket信息,然后校驗用戶名密碼是否合理。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | /// <summary> ????/// 自定義此特性用于接口的身份驗證 ????/// </summary> ????public?class?RequestAuthorizeAttribute : AuthorizeAttribute ????{ ????????//重寫基類的驗證方式,加入我們自定義的Ticket驗證 ????????public?override?void?OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) ????????{ ????????????//從http請求的頭里面獲取身份驗證信息,驗證是否是請求發起方的ticket ????????????var?authorization = actionContext.Request.Headers.Authorization; ????????????if?((authorization !=?null) && (authorization.Parameter !=?null)) ????????????{ ????????????????//解密用戶ticket,并校驗用戶名密碼是否匹配 ????????????????var?encryptTicket = authorization.Parameter; ????????????????if?(ValidateTicket(encryptTicket)) ????????????????{ ????????????????????base.IsAuthorized(actionContext); ????????????????} ????????????????else ????????????????{ ????????????????????HandleUnauthorizedRequest(actionContext); ????????????????} ????????????} ????????????//如果取不到身份驗證信息,并且不允許匿名訪問,則返回未驗證401 ????????????else ????????????{ ????????????????var?attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>(); ????????????????bool?isAnonymous = attributes.Any(a => a?is?AllowAnonymousAttribute); ????????????????if?(isAnonymous)?base.OnAuthorization(actionContext); ????????????????else?HandleUnauthorizedRequest(actionContext); ????????????} ????????} ? ????????//校驗用戶名密碼(正式環境中應該是數據庫校驗) ????????private?bool?ValidateTicket(string?encryptTicket) ????????{ ????????????//解密Ticket ????????????var?strTicket = FormsAuthentication.Decrypt(encryptTicket).UserData; ? ????????????//從Ticket里面獲取用戶名和密碼 ????????????var?index = strTicket.IndexOf("&"); ????????????string?strUser = strTicket.Substring(0, index); ????????????string?strPwd = strTicket.Substring(index + 1); ? ????????????if?(strUser ==?"admin"?&& strPwd ==?"123456") ????????????{ ????????????????return?true; ????????????} ????????????else ????????????{ ????????????????return?false; ????????????} ????????} ????} |
3.2、在具體的Api接口增加我們上面自定義類的特性
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | [RequestAuthorize] ????public?class?ChargingController : ApiController ????{ ????????/// <summary> ????????/// 得到所有數據 ????????/// </summary> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????public?string?GetAllChargingData() ????????{ ????????????return?"Success"; ????????} ? ????????/// <summary> ????????/// 得到當前Id的所有數據 ????????/// </summary> ????????/// <param name="id">參數Id</param> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????public?string?GetAllChargingData(string?id) ????????{ ????????????return?"ChargingData"?+ id; ????????} ? ????} |
增加了特性標注之后,每次請求這個API里面的接口之前,程序會先進入到我們override過的?OnAuthorization()?方法里面,驗證通過之后,才會進到相應的方法里面去執行,否則返回401。
四、優化
?通過上面的幾步,基本就能達到我們想要的身份認證的效果,但是總是感覺不太方便,主要不太方便的點有以下幾個。
關于以上兩點,我們優化下
1、解決API的問題
在API里面加一個公共的父類,在父類上面標注?[RequestAuthorize]?即可。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | namespace?WebApiCORS.Controllers { ????[RequestAuthorize] ????[EnableCors(origins:?"*", headers:?"*", methods:?"*")] ????public?class?BaseApiController : ApiController ????{ ????} } namespace?WebApiCORS.Controllers { ????public?class?ChargingController : BaseApiController ????{ ????????/// <summary> ????????/// 得到所有數據 ????????/// </summary> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????public?string?GetAllChargingData() ????????{ ????????????return?"Success"; ????????} ? ????????/// <summary> ????????/// 得到當前Id的所有數據 ????????/// </summary> ????????/// <param name="id">參數Id</param> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????public?string?GetAllChargingData(string?id) ????????{ ????????????return?"ChargingData"?+ id; ????????} } } |
注意:我們登錄的請求是不需要驗證的,因為登錄的時候還沒有產生票據,所以登錄的API不能夠繼承?BaseApiController?
2、解決ajax的問題
新建一個文件Jquery_ajax_extention.js
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | (function?($) { ????//1.得到$.ajax的對象 ????var?_ajax = $.ajax; ????$.ajax =?function?(options) { ????????//2.每次調用發送ajax請求的時候定義默認的error處理方法 ????????var?fn = { ????????????error:?function?(XMLHttpRequest, textStatus, errorThrown) { ????????????????toastr.error(XMLHttpRequest.responseText,?'錯誤消息', { closeButton:?true, timeOut: 0, positionClass:?'toast-top-full-width'?}); ????????????}, ????????????success:?function?(data, textStatus) { }, ????????????beforeSend:?function?(XHR) { }, ????????????complete:?function?(XHR, TS) { } ????????} ????????//3.擴展原生的$.ajax方法,返回最新的參數 ????????var?_options = $.extend({}, { ????????????error:?function?(XMLHttpRequest, textStatus, errorThrown) { ????????????????fn.error(XMLHttpRequest, textStatus, errorThrown); ????????????}, ????????????success:?function?(data, textStatus) { ????????????????fn.success(data, textStatus); ????????????}, ????????????beforeSend:?function?(XHR) { ????????????????XHR.setRequestHeader('Authorization',?'BasicAuth '?+ Ticket); ????????????????fn.beforeSend(XHR); ????????????}, ????????????complete:?function?(XHR, TS) { ????????????????fn.complete(XHR, TS); ????????????} ????????}, options); ????????//4.將最新的參數傳回ajax對象 ????????_ajax(_options); ????}; })(jQuery); |
引用這個js后再發送ajax不必在每個請求的beforeSend里面寫了。
3、解決特殊不想使用驗證的方法
如果我們某些方法不想使用驗證,使得它可以讓匿名用戶訪問,我們可以在方法的上面加特性標注?[AllowAnonymous]?,申明該方法運行匿名訪問。比如:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public?class?ChargingController : BaseApiController ????{ ????????/// <summary> ????????/// 得到所有數據 ????????/// </summary> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????public?string?GetAllChargingData() ????????{ ????????????return?"Success"; ????????} ? ????????/// <summary> ????????/// 得到當前Id的所有數據 ????????/// </summary> ????????/// <param name="id">參數Id</param> ????????/// <returns>返回數據</returns> ????????[HttpGet] ????????[AllowAnonymous] ????????public?string?GetAllChargingData(string?id) ????????{ ????????????return?"ChargingData"?+ id; ????????} } |
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的【转】WebApi 身份认证解决方案:Basic基础认证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 民生白条联名卡怎么申请?有什么要求?
- 下一篇: 【转】1.C Task.Complete