系列導航地址http://www.cnblogs.com/fzrain/p/3490137.html
前言
這一篇文章我們主要來探討一下Web Api的安全性,到目前為止所有的請求都是走的Http協議(http://),因此客戶端與服務器之間的通信是沒有加密的。在本篇中,我們將在“StudentController”中添加身份驗證功能——通過驗證用戶名與密碼來判斷是否是合法用戶。眾所周知,對於機密信息的傳遞,我們應該使用安全的Http協議(https://)來傳輸
在Web Api中強制使用Https
我們可以在IIS級別配置整個Web Api來強制使用Https,但是在某些情況下你可能只需要對某一個action強制使用Https,而其他的方法仍使用http。
為了實現這一點,我們將使用Web Api中的filters——filter(過濾器)的主要作用就是可以在我們執行方法之前執行一段代碼。沒接觸過得可以通過下圖簡單理解下,大神跳過:
我們新創建的filter將用來檢測是否是安全的,如果不是安全的,filter將終止請求並返回相應:請求必須是https。
具體做法:創建一個filter繼承自AuthorizationFilterAttribute,重寫OnAuthorization來實現我們的需求。
在網站根目錄下創建“Filters”文件夾,新建一個類“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"); } } } }
在上面代碼中,我們通過actionContext參數拿到request和response對象,我們判斷客戶端的請求:如果不是https,那么直接響應客戶端應該使用https。
在這里,我們需要區分請求是Get還是其他(Post,Delete,Put),因為對於使用了Http的Get請求來訪問資源,我們將使用https創建一個連接並添加在響應Header的Location中。這樣做了之后客戶端就會自動使用https來發送Get請求了。
對於非Get請求,直接返回404,並通知客戶端必須使用https來請求
如果我們打算在整個項目中使用,那么在“WebAPIConfig”類中做如下設置:
public static void Register(HttpConfiguration config) { config.Filters.Add(new ForceHttpsAttribute()); }
如果我們相對具體的Controller或Action設置時,可以做如下設置:
//對於整個Controller強制使用Https
[Learning.Web.Filters.ForceHttps()] public class CoursesController : BaseApiController { //僅對這個方法強制使用Https
[Learning.Web.Filters.ForceHttps()] public HttpResponseMessage Post([FromBody] CourseModel courseModel) { } }
使用Basic Authentication驗證用戶
到目前為止,我們提供的所有Api都是公開的,任何人都能訪問。但在真是場景中卻是不可取的,對於某些數據,只有通過認證的用戶才能訪問,我們這里有兩個地方恰好說明這一點:
1.當客戶端發送Get請求道“http://{your_port}/api/students/{userName}“的時候.例如:通過上述URI訪問userNme為“TaiseerJoudeh”的信息時,我們必須讓客戶端提供TaiseerJoudeh相應的用戶名和密碼,對於沒有提供驗證信息的用戶我們就不讓訪問,因為學生信息包含一些重要的私人信息(email,birthday等)。
2.當客戶端發送Post請求到“http://{your_port}/api/courses/2/students/{userName}“的時候,這意味着給學生選課,我們可以想一下,這里如果不做驗證,那么所有人都能隨便給某個學生選課,那么不就亂了么。
對於上面的場景,我們使用Basic Authentication來進行身份驗證,主要思路是使用filter從請求header部分獲取身份信息,校驗驗證類型是否為“basic”,然后校驗內容,正確就放行,否則返回401 (Unauthorized)狀態碼。
在上代碼前,解釋一下下basic authentication:
什么是basic authentication?
它意味着在正式處理Http請求之前對請求者身份的校驗,這可以防止服務器受到DoS攻擊(Denial of service attacks)。原理是:客戶端在發送Http請求的時候在Header部分提供一個基於Base64編碼的用戶名和密碼,形式為“username:password”,消息接收者(服務器)進行驗證,通過后繼續處理請求。
由於用戶名和密碼僅適用base64編碼,因此為了保證安全性,basic authentication通常是基於SSL連接(https)
為了在我們的api中使用,創建一個類“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'"); } }
我們重寫了“OnAuthorization”,實現如下功能:
1.從請求Header中獲取校驗數據
2.判斷驗證信息類型為“basic”並包含base64編碼
3.將base64編碼轉化為string,並提取用戶名和密碼
4.校驗提供的驗證信息是否與訪問的資源信息相同(學生的詳細信息只能由他自己訪問)
5.去數據庫校驗用戶名及密碼
6.如果校驗通過,則設置Thread的CurrentPrincipal,使本次接下來的請求都是通過校驗的。
7.校驗沒通過,返回401(Unauthorized)並添加一個WWW-Authenticate響應頭,根據這個請求,客戶端可以添加相應的驗證信息
在代碼中實現起來就很簡單了,上兩個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) { } }
測試成果
使用測試工具發送如下請求:
由於沒有提供身份驗證,於是得到如下響應:
取消:
去數據庫找到對應的用戶名和密碼輸入,得到如下結果:
總結
因為 Base Authentication 的安全性較差,但對於無 Cookie 的 Web Api 來說,應用上非常的簡單和方便。
Base Authentication 最大的缺點是憑據會被瀏覽器緩存——直到你關閉瀏覽器為止。如果你已經對某個URI獲得了授權,瀏覽器就會在授權頭發送相應的憑據,這使其更容易受到跨站點請求偽造(CSRF)攻擊
Base Authentication 通常需要使用HTTPS方式進行加密處理。