系列導(dǎo)航地址http://www.survivalescaperooms.com/fzrain/p/3490137.html
這一篇文章我們主要來(lái)探討一下Web Api的安全性,到目前為止所有的請(qǐng)求都是走的Http協(xié)議(http://),因此客戶(hù)端與服務(wù)器之間的通信是沒(méi)有加密的。在本篇中,我們將在“StudentController”中添加身份驗(yàn)證功能——通過(guò)驗(yàn)證用戶(hù)名與密碼來(lái)判斷是否是合法用戶(hù)。眾所周知,對(duì)于機(jī)密信息的傳遞,我們應(yīng)該使用安全的Http協(xié)議(https://)來(lái)傳輸
我們可以在IIS級(jí)別配置整個(gè)Web Api來(lái)強(qiáng)制使用Https,但是在某些情況下你可能只需要對(duì)某一個(gè)action強(qiáng)制使用Https,而其他的方法仍使用http。
為了實(shí)現(xiàn)這一點(diǎn),我們將使用Web Api中的filters——filter(過(guò)濾器)的主要作用就是可以在我們執(zhí)行方法之前執(zhí)行一段代碼。沒(méi)接觸過(guò)得可以通過(guò)下圖簡(jiǎn)單理解下,大神跳過(guò):

我們新創(chuàng)建的filter將用來(lái)檢測(cè)是否是安全的,如果不是安全的,filter將終止請(qǐng)求并返回相應(yīng):請(qǐng)求必須是https。
具體做法:創(chuàng)建一個(gè)filter繼承自AuthorizationFilterAttribute,重寫(xiě)OnAuthorization來(lái)實(shí)現(xiàn)我們的需求。
在網(wǎng)站根目錄下創(chuàng)建“Filters”文件夾,新建一個(gè)類(lèi)“ForceHttpsAttribute”繼承自“System.Web.Http.Filters.AuthorizationFilterAttribute”,下面上代碼:
public class ForceHttpsAttribute : AuthorizationFilterAttribute { public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { var request = actionContext.Request; if (request.RequestUri.Scheme != Uri.UriSchemeHttps) { var html = "<p>Https is required</p>"; if (request.Method.Method == "GET") { actionContext.Response = request.CreateResponse(HttpStatusCode.Found); actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html"); UriBuilder httpsNewUri = new UriBuilder(request.RequestUri); httpsNewUri.Scheme = Uri.UriSchemeHttps; httpsNewUri.Port = 443; actionContext.Response.Headers.Location = httpsNewUri.Uri; } else { actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound); actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html"); } } } }在上面代碼中,我們通過(guò)actionContext參數(shù)拿到request和response對(duì)象,我們判斷客戶(hù)端的請(qǐng)求:如果不是https,那么直接響應(yīng)客戶(hù)端應(yīng)該使用https。
在這里,我們需要區(qū)分請(qǐng)求是Get還是其他(Post,Delete,Put),因?yàn)閷?duì)于使用了Http的Get請(qǐng)求來(lái)訪(fǎng)問(wèn)資源,我們將使用https創(chuàng)建一個(gè)連接并添加在響應(yīng)Header的Location中。這樣做了之后客戶(hù)端就會(huì)自動(dòng)使用https來(lái)發(fā)送Get請(qǐng)求了。
對(duì)于非Get請(qǐng)求,直接返回404,并通知客戶(hù)端必須使用https來(lái)請(qǐng)求
如果我們打算在整個(gè)項(xiàng)目中使用,那么在“WebAPIConfig”類(lèi)中做如下設(shè)置:
public static void Register(HttpConfiguration config) { config.Filters.Add(new ForceHttpsAttribute()); }如果我們相對(duì)具體的Controller或Action設(shè)置時(shí),可以做如下設(shè)置:
//對(duì)于整個(gè)Controller強(qiáng)制使用Https
[Learning.Web.Filters.ForceHttps()] public class CoursesController : BaseApiController { //僅對(duì)這個(gè)方法強(qiáng)制使用Https[Learning.Web.Filters.ForceHttps()] public HttPResponseMessage Post([FromBody] CourseModel courseModel) { }}
到目前為止,我們提供的所有Api都是公開(kāi)的,任何人都能訪(fǎng)問(wèn)。但在真是場(chǎng)景中卻是不可取的,對(duì)于某些數(shù)據(jù),只有通過(guò)認(rèn)證的用戶(hù)才能訪(fǎng)問(wèn),我們這里有兩個(gè)地方恰好說(shuō)明這一點(diǎn):
1.當(dāng)客戶(hù)端發(fā)送Get請(qǐng)求道“http://{your_port}/api/students/{userName}“的時(shí)候.例如:通過(guò)上述URI訪(fǎng)問(wèn)userNme為“TaiseerJoudeh”的信息時(shí),我們必須讓客戶(hù)端提供TaiseerJoudeh相應(yīng)的用戶(hù)名和密碼,對(duì)于沒(méi)有提供驗(yàn)證信息的用戶(hù)我們就不讓訪(fǎng)問(wèn),因?yàn)閷W(xué)生信息包含一些重要的私人信息(email,birthday等)。
2.當(dāng)客戶(hù)端發(fā)送Post請(qǐng)求到“http://{your_port}/api/courses/2/students/{userName}“的時(shí)候,這意味著給學(xué)生選課,我們可以想一下,這里如果不做驗(yàn)證,那么所有人都能隨便給某個(gè)學(xué)生選課,那么不就亂了么。
對(duì)于上面的場(chǎng)景,我們使用Basic Authentication來(lái)進(jìn)行身份驗(yàn)證,主要思路是使用filter從請(qǐng)求header部分獲取身份信息,校驗(yàn)驗(yàn)證類(lèi)型是否為“basic”,然后校驗(yàn)內(nèi)容,正確就放行,否則返回401 (Unauthorized)狀態(tài)碼。
在上代碼前,解釋一下下basic authentication:
它意味著在正式處理Http請(qǐng)求之前對(duì)請(qǐng)求者身份的校驗(yàn),這可以防止服務(wù)器受到DoS攻擊(Denial of service attacks)。原理是:客戶(hù)端在發(fā)送Http請(qǐng)求的時(shí)候在Header部分提供一個(gè)基于Base64編碼的用戶(hù)名和密碼,形式為“username:passWord”,消息接收者(服務(wù)器)進(jìn)行驗(yàn)證,通過(guò)后繼續(xù)處理請(qǐng)求。
由于用戶(hù)名和密碼僅適用base64編碼,因此為了保證安全性,basic authentication通常是基于SSL連接(https)
為了在我們的api中使用,創(chuàng)建一個(gè)類(lèi)“LearningAuthorizeAttribute”繼承自System.Web.Http.Filters.AuthorizationFilterAttribute
public class LearningAuthorizeAttribute : AuthorizationFilterAttribute { [Inject] public LearningRepository TheRepository { get; set; } public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //forms authentication Case that user is authenticated using forms authentication//so no need to check header for basic authentication. if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { return; } var authHeader = actionContext.Request.Headers.Authorization; if (authHeader != null) { if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrWhiteSpace(authHeader.Parameter)) { var credArray = GetCredentials(authHeader); var userName = credArray[0]; var password = credArray[1]; if (IsResourceOwner(userName, actionContext)) { //You can use Websecurity or asp.net memebrship provider to login, for //for he sake of keeping example simple, we used out own login functionality if (TheRepository.LoginStudent(userName, password)) { var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null); Thread.CurrentPrincipal = currentPrincipal; return; } } } } HandleUnauthorizedRequest(actionContext); } private string[] GetCredentials(System.Net.Http.Headers.AuthenticationHeaderValue authHeader) { //Base 64 encoded string var rawCred = authHeader.Parameter; var encoding = Encoding.GetEncoding("iso-8859-1"); var cred = encoding.GetString(Convert.FromBase64String(rawCred)); var credArray = cred.Split(':'); return credArray; } private bool IsResourceOwner(string userName, System.Web.Http.Controllers.HttpActionContext actionContext) { var routeData = actionContext.Request.GetRouteData(); var resourceUserName = routeData.Values["userName"] as string; if (resourceUserName == userName) { return true; } return false; } private void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='eLearning' location='http://localhost:8323/account/login'"); } }
我們重寫(xiě)了“OnAuthorization”,實(shí)現(xiàn)如下功能:
1.從請(qǐng)求Header中獲取校驗(yàn)數(shù)據(jù)
2.判斷驗(yàn)證信息類(lèi)型為“basic”并包含base64編碼
3.將base64編碼轉(zhuǎn)化為string,并提取用戶(hù)名和密碼
4.校驗(yàn)提供的驗(yàn)證信息是否與訪(fǎng)問(wèn)的資源信息相同(學(xué)生的詳細(xì)信息只能由他自己訪(fǎng)問(wèn))
5.去數(shù)據(jù)庫(kù)校驗(yàn)用戶(hù)名及密碼
6.如果校驗(yàn)通過(guò),則設(shè)置Thread的CurrentPrincipal,使本次接下來(lái)的請(qǐng)求都是通過(guò)校驗(yàn)的。
7.校驗(yàn)沒(méi)通過(guò),返回401(Unauthorized)并添加一個(gè)WWW-Authenticate響應(yīng)頭,根據(jù)這個(gè)請(qǐng)求,客戶(hù)端可以添加相應(yīng)的驗(yàn)證信息
在代碼中實(shí)現(xiàn)起來(lái)就很簡(jiǎn)單了,上兩個(gè)Attribute就完了:
public class StudentsController : BaseApiController { [LearningAuthorizeAttribute] public HttpResponseMessage Get(string userName) { } }public class EnrollmentsController : BaseApiController { [LearningAuthorizeAttribute] public HttpResponseMessage Post(int courseId, [FromUri]string userName, [FromBody]Enrollment enrollment) { } }使用測(cè)試工具發(fā)送如下請(qǐng)求:
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注