系列導航地址http://www.cnblogs.com/fzrain/p/3490137.htmlhtml
這一篇文章咱們主要來探討一下Web Api的安全性,到目前爲止全部的請求都是走的Http協議(http://),所以客戶端與服務器之間的通訊是沒有加密的。在本篇中,咱們將在「StudentController」中添加身份驗證功能——經過驗證用戶名與密碼來判斷是不是合法用戶。衆所周知,對於機密信息的傳遞,咱們應該使用安全的Http協議(https://)來傳輸git
咱們能夠在IIS級別配置整個Web Api來強制使用Https,可是在某些狀況下你可能只須要對某一個action強制使用Https,而其餘的方法仍使用http。github
爲了實現這一點,咱們將使用Web Api中的filters——filter(過濾器)的主要做用就是能夠在咱們執行方法以前執行一段代碼。沒接觸過得能夠經過下圖簡單理解下,大神跳過:web
咱們新建立的filter將用來檢測是不是安全的,若是不是安全的,filter將終止請求並返回相應:請求必須是https。數據庫
具體作法:建立一個filter繼承自AuthorizationFilterAttribute,重寫OnAuthorization來實現咱們的需求。api
在網站根目錄下建立「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) { } }
到目前爲止,咱們提供的全部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:
它意味着在正式處理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方式進行加密處理。