ASP.NET MVC和WebAPI已經是.NET Web部分的主流,剛開始時兩個公用同一個管道,之後為了更加的輕量化(WebAPI是對WCF Restful的輕量化),WebAPI使用了新的管道,因此兩者相關類的名稱空間有細微差異,在使用時需要注意。
WebAPI學習系列目錄如下,歡迎您的閱讀!
-
WebAPI與ASP.NET路由的異同
ASP.NET MVC的路由:Routes(RouteCollection)的執行緒安全,讀寫鎖,GetReadLock, GetWriteLock。RouteTable.Routes.MapPageRoute(…);
名稱空間為System.Web.Routing中
WebAPI的路由:首先介紹其相關型別,他們均是對Http報文的簡易封裝,System.Net.Http(HttpRequestMessage, HttpResponseMessage)。
名稱空間為System.Web.Http.Routing中
HttpRouteHandler。
-
訊息處理管道
還記的ASP.NET MVC中的核心是HttpHandler,而在WebAPI中其管道處理器是HttpMessageHandler。在實際中其通過職責鏈模式將委託通過InnerHandler(DelegationHandler)方式進行處理。其中,其中這個管道最開始的是httpServer,最末端的是HttpRoutingDispatcher(均在System.Web.Http名稱空間下,支援非同步模型),P108
Tip:額外想想也能理解WebAPI管道為什麼更加輕量化,因而它只需要處理Json等型別資料,不需要考慮如頁面、JS、靜態資源等內容。
-
常見特性
Class: [RoutePrefix(“api/demo”)],針對具體類的路由設定,相對RouteConfig,粒度更細。
Method: [Route(“action”)]
[HttpGet], [HttpPost]
關於Web服務,其中比較難的概念一般都集中在安全,其相關概念非常的多,包括Windows相關認證模式、Forms認證、第三方認證、跨域訪問等,接下來一一介紹。此外還會附加HttpClient、IOC框架的選擇、服務冪等性、SignalR、EntLib中的EHAB等概念。
.NET安全模型:
Identiy表示使用者身份, Identity AuthenticationType, IsAuthenticated, Name},常見的Identity有WindowsIdentity, FormsIdentity, GenericIdentity。P556
IPrincipal, 被成功實施授權的實體,等價於身份加角色,包括WindowsPrincipal(windows的許可權組),GenericPrincipal, RolePrincipal(Membership元件和Roles元件) 。常見的認證方式通過”質詢-應答”(challenge-Response)方式。
常見http認證方式,Basic和Digest,前者使用將認證憑證(使用者名稱+密碼)通過base64編碼而未加密,但我們可以使用https傳輸來解決機密性問題。與此相關的兩個過濾器, AuthenticationFilter和AuthorizationFilter。
補充ActionFilter概念,比如請求涉及大量運算,並且輸入和輸出一一對應(即相同的輸入有相同的輸出),那麼可以考慮快取Action。P585
Windows認證模式(均通過在IIS中設定身份認證模式)
WebHost寄宿下的安全:Windows認證模式,通過Basic, Digest認證方案,最終採用NTLM或者Kerberos協議。認證使用者的Principal體現在HttpContext、當前執行緒、ApiControlelr。Keep-Alive,Fidder檢視呼叫。
名稱 | 狀態 | 響應型別 |
Active Directory客戶端證照身份驗證 | 已禁用 | HTTP 401 質詢 |
ASP.NET 模式 | 已禁用 | |
Forms身份驗證 | 已禁用 | HTTP 302 登入/重定向 |
Windows身份驗證 | 已禁用 | HTTP 401 質詢 |
基本身份驗證(Windows/Basic) | 已禁用 | HTTP 401 質詢 |
匿名身份驗證 | 已禁用 | |
摘要式身份驗證(Windows/digest) | 已啟用 | HTTP 401 質詢 |
-
Basic認證
現在都是HTTP401 質詢模型,只有forms是http 302 登入/重定向。這個關於basic的質詢方式很有意思,就是當你請求時,出現http 401,會要求你輸入使用者名稱密碼,輸入後你輸入的使用者名稱和密碼會被base64編碼傳送的伺服器,形式是Basic YWRtaW46YWRtaW4=,這部分的head就是authentication。檢視windows的憑據管理器,賬號密碼木有問題,但仍然不能通過驗證,非常的傷感,自己試著加上域cn1,結果OK了,感覺棒棒噠,哈哈,說明asp.net安全模型和windows有很好的整合性。
Basic模式的流程是,瀏覽器向伺服器IIS以匿名的方式傳送GET請求,IIS回覆一個401 Unauthorized的響應,該響應用”www-authenticate”報頭告訴客戶端採用的認證方案(basic)和對應的領域(Realm, localhost)。瀏覽器收到響應彈出登入對話方塊,收集輸入的賬號密碼組成字串作為認證憑證,接下來,瀏覽器再次傳送請求,在authorization包頭中攜帶認證的方案和使用者的憑證Basic YWRtaW46YWRtaW4=,IIS解密後認證,action順利執行。
Base64:是網路常見的用於傳輸8bit位元組程式碼的編碼方式,用在http表單(包括隱藏的表單域)和http GET url中,base64編碼的資訊具有不可讀性,但不具有機密性,使用時需要注意應用場景。
-
Digest認證
Digest認證僅僅適用於Domain模式,如果基於WorkGroup模式,也無法使用,接下來通過fiddler看看相應的HTTP訊息頭。
HTTP 401的響應:
WWW-Authenticate: Digest qop=”auth”,algorithm=MD5-sess,nonce=”+Upgraded+v1e4fcae181b935afc3d94f30f5033141a25e3c7e4b83bd101c60cf10ea425a352c8959c8d47e643e5fc38f90cffe411be5f7a99033900ae4d”,charset=utf-8,realm=”Digest”
Digest認證傳輸使用者憑證的雜湊碼,而不是明文。客戶端首先匿名向伺服器傳送GET請求,伺服器返回一個401響應,這個響應包含一個”WWW-Authenticate”報頭,攜帶的資訊包括。
引數 | 解釋 |
Digest | 認證方案 |
Realm=”Digest” | 領域 |
Algorithm | 表示服務端支援的雜湊演算法,MD5-sess |
Nonce | 服務端生成的唯一性標示,一般來說,IIS會利用當前時間戳以及請求的Etag(被請求變數的實體值)報頭值來生成這個nonce |
Qop(Quanlity of Protection) | IIS採用qop通知客戶端採用的訊息保護等級,可選值包括auth(authentication), auth-int(authentication-integrity),前者僅限於基本的認證,後者還確保傳輸內容的一致性。 |
輸入賬號密碼的再一次請求(響應為200成功):
Authorization: Digest username=”cn1\she_s”, realm=”Digest”, nonce=”+Upgraded+v1e4fcae181b935afc3d94f30f5033141a25e3c7e4b83bd101c60cf10ea425a352c8959c8d47e643e5fc38f90cffe411be5f7a99033900ae4d”, uri=”/Sory.Entertainment.WebAPI/”, algorithm=MD5-sess, response=”9a6cb99fad4404cdd521e5db432f6b09″, qop=”auth”, nc=00000001, cnonce=”c77c05d93544b363″
其相關引數為:
引數 | 解釋 |
Username | 代表客戶端的使用者名稱,看來使用者名稱還是可以擷取的 |
Qop | 最終採用的訊息保護等級, qop=”auth” |
Algorithm | 最終採用的加密演算法,MD5-sess |
Nonce | 實際就是伺服器端生成的nonce |
cnonce | 客戶端生成的nonce(c代表client),它可以對請求內容簽名以確保內容未被篡改,可以幫助客戶端對服務端實施認證(服務端能夠證明知道該nonce,這些很適合防禦跨站請求偽造的防禦) |
Nc(nonce count) | 它表示客戶端針對同一個nonce傳送請求的數量,一位著這是隨著請求不斷增加的數字,IIS可以通過nc代表的數字來防止”重放攻擊”,它會維護每個nonce的nc,如果請求攜帶的nc比這個少,會被認為是不合法的請求。 |
IIS在接受到第二次請求後,它先對請求進行合法性校驗(比如nc的合法性),然後從Authentication報頭提取使用者名稱、nonce和加密演算法計算出針對使用者名稱真正的Digest,最終利用它與請求中提供的Digest進行比較確認密碼的正確性,完成客戶端認證。
Tip:
<script type=”application/json” id=”__browserLink_initializationData”>
{“appName”:”Firefox”,”requestId”:”ee1fc1d3f30e4b4cba937703bee3ce10″}
</script>
<script type=”text/javascript” src=”http://localhost:13820/69ea419b05a141aaa5111affa4bb02fe/browserLink” async=”async”></script>
這部分如何理解,與jsonP相關?
-
整合Windows認證, P610
無論問basic還是Digest認證,如果使用瀏覽器做客戶端,第一次訪問總需要在彈出框中輸入,非常繁瑣,並且密碼在網路中傳輸,有安全風險,一般採用加鹽的方式避免。整合Windows認證可以很好解決該問題,它預設以登入機器的Windows賬號的名義來訪問被授權的資源沒,使用者的密碼被包含在請求攜帶的安全令牌中,非常的方便,該方式最終使用NTLM和Kerberos協議來完成。
NTLM協議(比較陳舊):採用質詢/應答(Challenge/Response)訊息交換模式,DC域控制器儲存所用使用者的相關資訊。基本流程為:步驟1,使用者輸入賬戶密碼登入主機,主機會快取輸入密碼的雜湊值,原始密碼會丟失。如果檢視訪問伺服器資源,需要向對方傳送請求,請求中包含一個明文表示的使用者名稱;步驟2,服務端接受請求,生成16位隨機數(稱為質詢challenge),儲存起來後以明文的形式傳送給客戶端;和Digest請求中nonce的意圖完全一致;步驟3,客戶端收到服務端的質詢後,用在步驟1中儲存的密碼雜湊值對其加密,然後將加密後的質詢傳送給服務端;步驟4,服務端收到加密質詢後,會向DC傳送針對客戶端的驗證請求(請求中包括,使用者名稱、客戶端密碼加密後的質詢和原始的質詢);步驟5、6,DC根據使用者名稱獲得密碼雜湊值,對原始質詢加密,再與服務端傳送的質詢比較,一致就為驗證通過,否則失敗。
Kerberos:這東西也算是困擾了小弟很多年,老看到,尤其每次註冊windows時,呵呵,你懂得。實際上它是一種更搞笑安全的認證協議,過程更加的複雜,與NTLM相似,也包含三部分,客戶端、伺服器和KDC(Key Distribution Center,在windows域中,KDC有DC擔當)。Kerberos實際是一種基於票據(Ticket)的認證方式,客戶端要訪問服務端的資源,首先要購買服務端認可的票據。也就是說,客戶端在訪問伺服器前要先買好票,等待伺服器驗票後才能入場,但這票不能直接購買,首先需要認購權證(和糧票,股票認購權證相似)。這個認購權證和進入伺服器的入場券均由KDC發售,感覺各種繞……相關詳細內容暫時放一放。
在IIS中使用windows整合驗證時,會看到provider的設定,有”negotiate”和”NTLM”兩個選項,預設使用前者,其Provider包括”Negotiate: Kerberos”,當然也可以自定義。此外,客戶端需要在IE設定-》高階中,開啟Windows整合認證,預設是開啟的。在使用HttpClient時,可以使用以下方式,簡化呼叫。
1 2 3 4 |
HttpClientHandler handler = new HttpClientHanlder(); 方式1: Handler.UseDefaultCredentials = true; 方式2: handler.Credentials = new NetworkCredentials("username", "password", "domainname"); Using(HttpClient client = new HttpClient(handler)){} |
可與獨立於windows系統的認證方式,以前做webform時,forms認證是用的最多的,當時還一直以為forms驗證也需要和windows相關,尤其是和webForm中的form相關,現在想想挺幼稚的,同時這個驗證可以和membership很好的搭配在一起。但實際上這種驗證方式是獨立的,適合自行維護使用者賬號和密碼的場景,也是絕大部分專案的場景。那麼接下來介紹forms認證是如何進行的,努力使自己真正的走出誤區。
Forms認證的流程設計4次的訊息交換,其具體步驟如下所示。
步驟1:使用者通過瀏覽器匿名向IIS發起請求,假設地址為”/home”,它會收到狀態為”302, Found”的相應,這是一個用於實現”重定向”的http響應,它通過location報頭表示的重定向地址指向登入的頁面,之前訪問的地址將作為查詢字串returnURL的值。
步驟2:瀏覽器接受到該請求後,針對重定向的地址傳送請求,登入頁面最終被呈現在瀏覽器。
步驟3:使用者輸入正確的使用者名稱密碼後提交表單,伺服器在接受到請求之後提取它們對使用者實施認證,認證成功後,它會生成一個安全令牌或者認證票據。接下來,通過查詢字串returnURL表示的原始請求地址,作為另一個狀態為”302, Found”響應的Location報頭,而經過加密/簽名的安全令牌作為該響應的Cookie。
步驟4:這個代表安全令牌的Cookie將自動附加到瀏覽器後續的請求中,伺服器直接利用它對請求實施認證。Cookie的名稱、過期策略以及採用的保護等級均可以通過配置來控制。在禁用Cookie的情況下,安全令牌會直接作為URL的一部分傳送。
Tip:
首先想補充補充的是原來的forms認證的配置通過如下配置,加上在login相關方法上加上[AllowAnonymous],然後IIS中設定啟用匿名認證和forms認證即可。
1 2 3 4 5 6 |
<authentication mode="Forms" > <forms loginUrl="~/account/login"/> </authentication> <authorization> <deny users="?"/> </authorization> |
在ASP.NET 5之後的版本配置方式有一些變化,為了和第三方認證OAuth整合,不需要配置檔案的配置,而是通過如下程式碼配置,如果新增往往會出錯。
1 2 3 4 5 |
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login") }); |
Cookie採用的保護等級,在IE設定中包括6個隱私等級,對cookie的管理程度各不相同,從cookie完全不可讀寫,到完全可讀寫,預設的等級為中,阻止沒有精簡隱私策略的第三方cookie。
昨天和同事聊天時,還注意到有的專案的logoff是直接跳轉頁面,而不是action,因此缺少清空session等伺服器端資訊的操作,在實際開發中也算是個易錯點。
之前介紹的認證方式,都要求密碼(token)在網路中進行傳輸,為了確保密碼不被竊取,需要用SSLTLS對傳輸的內容實施保護。其中涉及很多安全相關的基礎知識點,這兒只做簡要介紹。
-
非對稱加密:保證訊息機密性,涉及有一個公鑰和金鑰組成的金鑰對。
-
數字簽名:保證身份認證、防止抵賴Non-repudiation、訊息一致性
-
數字證照:Digital Certificate, 也稱為公鑰證照Public Key Certificate,比如12306要求在客戶端安裝的伺服器根證照,大部分基於X.509 V3證照標準,還可以稱為X.509證照。其實際上就是將某個金鑰對中的公鑰與某個主體Subject進行繫結的檔案,其內容包括版本號V3,序列號,簽名演算法(md5WithRSAEncryption)、頒發者(Issuer)、有效日期、主體資訊、主題公鑰和公鑰演算法,以及頒發者的數字簽名。
Tip:對於數字證照想說的是,一定要把它才分開了理解,大體包含三部分,公鑰資訊、簽名資訊和其他資訊。並且後面兩者都是為前者的安全送達服務的,簡而言之(如12306購買火車票場景,祝願大家都能買到過個幸福年,哈哈),網站通過要求使用者安裝根證照的方式將網站通訊金鑰對中的公鑰傳送給我,但為了保證這個過程的安全,就需要提供數字簽名的過程。就像將情報通訊密碼給我,並且簽上了FBI一樣,之後就可以用這個密碼進行通訊了。這個說的比較粗略,有些簡化,省略了認證權威機構和認證樹的概念。
關於SSL/TLS的概念,後者TLS(Transport Layer Security)其實是前者SSL(Secure Sockets Layer)的升級版本,TLS1.0就是SSL3.1,在IE的設定中,可以看到預設支援SSL 3.0和TLS1.0。而HTTPS是指HTTP與SSL/TLS的結合,像之前介紹的12306的安全,就是https的,也稱為弱安全模型,那麼強安全模型是什麼?那就是我們使用網銀時,大家都經歷安裝安全控制元件甚至使用U盾的過程,這兒就是強安全。簡單來說,強安全,指伺服器端和客戶端都要安裝對方的證照,相互認證;弱安全,指客戶端安裝伺服器證照,客戶端認證伺服器。接下來介紹請求Https網站的過程。
步驟1:客戶端向https站點傳送協商請求,包括客戶端所支援的加密演算法列表
步驟2:Https站點從演算法列表中選擇所能支援最合適安全級別的演算法(安全性和效率折衷),連同繫結到該站點的數字證照一併傳送給客戶端。
步驟3:客戶端接受證照後,通過驗證確認站點身份,成功後,生成一個隨機數,作為會話金鑰(Session key)快取在客戶端。客戶端採用發回的加密演算法,利用證照中的公鑰對該金鑰(Session Key)加密,加密後傳送給站點,站點解密獲得Session key。
步驟4:客戶端和服務端使用該Session key使用對稱加密演算法進行加解密。(對稱加密效率高,但金鑰管理難,因此採用結合兩者的方式,用非對稱加密管理金鑰,用金鑰來對稱加密,棒棒噠)
-
SSL/TLS在IIS中的應用
IIS對多種傳輸協議提供支援,包括http、Tcph和MSMQ等,站點繫結數字證照的方式喲很多,最方便的是用iis管理器,其步驟如下。
步驟1:在未目標站點新增https繫結之前,我們需要為它準備一張證照,可以用makeCert.exe工具,也可以使用iis管理器來建立自我簽名的證照。在IIS的特性列表中選擇”伺服器證照”,之後選擇”建立自我簽名證照”,命名和站點名稱相同即可。
步驟2:在IIS中,選擇我們指定的站點(Web Site),右鍵選擇編輯繫結,在網站繫結頁面新增https型別並選擇相應的證照,在瀏覽網站欄就可以看到http, 和https了。這時你就可以瀏覽網頁通過兩種不同的方式,當然你自定義的證照未被加入根證照,因此用https時,瀏覽器會顯示一個小紅叉。之後在httpclient部分,你也會發現,我們可以通過設定,跳過客戶端對伺服器證照的驗證,方便呼叫,不過不推薦。
網站的常見呼叫可以通過http和https兩種方式,但具體到某一個呼叫的時候,需要在”安全”和”效能”間權衡,但是認證過程必須採用https,將指定的action設定為[RequireHttps],那麼它就只能通過https協議訪問到。該特性實際是MVC提供的一個AuthenticationFilter,如果是一個普通請求,則會把該請求重定向到https的相應地址。這兒大家會注意到一個問題就是requiredHttps是MVC下的概念,那麼WebAPI中有對應概念麼?這個可以通過自定義的認證過濾器來處理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { //如果為https請求,授權通過 if (Uri.UriSchemeHttps == actionContext.Request.RequestUri.Scheme) { base.OnAuthorization(actionContext); return; } //對於http-get請求,將schema替換成https進行重定向 if (HttpMethod.Get == actionContext.Request.Method) { var requestUri = actionContext.Request.RequestUri; string location = string.Format("https://{0}/{1}", requestUri.Host, requestUri.LocalPath.TrimStart('/')); var actionResult = new RedirectResult(new Uri(location), actionContext.Request); actionContext.Response = actionResult.ExecuteAsync(new CancellationToken()).Result; } //採用其他http方法的請求直接認為是bad request actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "SSL Required" }; } |
一般來說,web應用的使用者認證均由自身完成,通過儲存使用者名稱和密碼並進行驗證,但這種方式在當前的網際網路場景下會有一下兩個主要問題:使用者需要註冊不同的賬號,記住和使用非常的麻煩了;對於應用提供者,大量認證系統會花費大量的精力。基於OAuth2.0(Open Authentication 2.0)的第三方認證模型的出現正好可以解決這個痛點,該模型藉助了Google, 微信等值得信賴的IT服務提供商。OAuth是定義一種協議幫助資源的擁有者在不提供自身憑證的前提下授權第三方應用以他的名義儲存保護的資源。
獲得資源擁有者授權的第三方應用獲取受保護的資源採用的不是授權者憑證,而是一個被稱為Access Token的安全令牌,Access Token頒發過程會涉及若干不同的角色。接下來通過一個蔣大師提到的簡單例子做相應的介紹。例如我們開發了一個整合了新浪微博認證用於釋出打折商品資訊的App,經過使用者授權之後它可以呼叫新浪微博的WebAPI獲取使用者的電子郵箱地址併發布相應的打折訊息。那麼OAuth在該場景下的作用是,使用者授權該應用以自己名義呼叫新浪微博的webAPI獲取自己的郵箱地址,涉及4個角色:資源擁有者,一般為終端使用者;客戶端應用,需要獲得資源擁有者授權並最終訪問受保護資源的應用;資源伺服器,最終承載資源的伺服器,一本為一個webAPI;授權伺服器,它對使用者和客戶端實施認證,並在使用者授權的情況下向客戶端應用頒發Access Token,在之前介紹的場景下,兩者合一,均為新浪微博。
一般來說,如果需要針對某個第三方認證服務來開發應用,需要向對應的認證服務提供商對應用進行註冊,成功後獲得AppID/AppSecet(名稱不一定一樣),實際工作中其實每個專案往往也有AppID。常見的,我們可以申請windows服務https://account.live.com/developers/applications/, 申請應用後可以獲取clientID和clientSecret,並且設定重定向的域名。這兒想提醒大家的一點,就是這個重定向設定可以是多個,並且一定要和你每一個請求的重定向設定對應起來,一旦沒有設定,windows live會報無法提供服務的錯誤。
這兒流程類似於Kerberos認證,首先客戶端獲取授權憑證,之後再購買訪問憑證,最後訪問資源。Authorization grant(縮寫AG)包含4中型別:Implicit,省略了獲取AG過程;Authorization Code, 標準模式,這個AG是一個授權碼;之後的兩種不太有價值,就不介紹了。
-
Implicit Authorization Grant簡寫IMP,例子如下(需要修改host), P640,比較麻煩的部分。
該模型中,通過獲取當前請求的AccessToken,之後呼叫Windows Live Connect提供的API(https://apis.live.net/v5.0/me)。不過在此之前,如果使用者未登陸到Windows Live,那麼首先會跳轉到登陸頁面,完成GetProfile呼叫後將json格式字串顯示在瀏覽器中。這兒的核心是,我們通過AuthenticateAttribute將AccessToken寫入Cookie中,這與Forms認證相似,不過從安全形度講,利用Cookie攜帶安全令牌會引起被稱為”跨站請求偽造CSRF, Cross-Site Request Forgery”的安全問題,所以用htto報頭來作為安全令牌的載體比較合理。在IAuthenticationFilter介面的ChallengeAsync用於在認證過程中向客戶端傳送”質詢”響應,如果AccessToken不存在,就像WindowLive授權頁面重定向,引數(response-type, redirect_uri, client, scope)以查詢字串形式提供。而IActionFilter中的方法ExecuteActionFilterAsync用於將AccessToken寫入cookie。程式碼如下所示:
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
public class DemoController : ApiController { public HttpResponseMessage GetProfile() { string accessToken; if (this.Request.TryGetAccessToken(out accessToken)) { using (var client = new HttpClient()) { string address = string.Format("https://apis.live.net/v5.0/me?access_token={0}", accessToken); return client.GetAsync(address).Result; } } return new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "No access token" }; } public class AccountController : Controller { [RequireHttps] public ActionResult CaptureToken(string requestUri) { ViewBag.RequestUri = requestUri; return View(); } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class AuthenticateAttribute : FilterAttribute, IAuthenticationFilter, IActionFilter { public const string CookieName = "AccessToken"; public string CaptureTokenUri { get; private set; } public AuthenticateAttribute(string captureTokenUri) { this.CaptureTokenUri = captureTokenUri; } public Task AuthenticateAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken) { return Task.FromResult<object>(null); } public Task ChallengeAsync(HttpAuthenticationChallengeContext context, System.Threading.CancellationToken cancellationToken) { string accessToken; if (!context.Request.TryGetAccessToken(out accessToken)) { string clientId = "00000000441745E6"; string redirectUri = string.Format("{0}?requestUri={1}", this.CaptureTokenUri, context.Request.RequestUri); string scope = "wl.signin%20wl.basic"; var uri = new StringBuilder(); uri.Append("https://login.live.com/oauth20_authorize.srf"); uri.Append("?response_type=token"); uri.AppendFormat("&redirect_uri={0}&client_id={1}&scope={2}", redirectUri, clientId, scope); context.Result = new RedirectResult(new Uri(uri.ToString()), context.Request); } return Task.FromResult<object>(null); } public Task<System.Net.Http.HttpResponseMessage> ExecuteActionFilterAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken, Func<Task<System.Net.Http.HttpResponseMessage>> continuation) { var response = continuation().Result; string accessToken; if (actionContext.Request.TryGetAccessToken(out accessToken)) { response.SetAccessToken(actionContext.Request, accessToken); } return Task.FromResult<HttpResponseMessage>(response); } CaptureToken.cshtml <html> <head> <title></title> <script src="@Url.Content("~/scripts/jquery-1.10.2.js")"></script> <script type="text/javascript"> $(document).ready(function () { var redirectUri = '@ViewBag.RequestUri'; if (redirectUri.indexOf('?') >= 0) { redirectUri += '&' + location.hash.slice(1) } else { redirectUri += '?' + location.hash.slice(1) } location.href = redirectUri; }); </script> </head> <body></body> </html> |
Tip:
location是javascript裡邊管理位址列的內建物件,比如location.href就管理頁面的url,用location.href=url就可以直接將頁面重定向url。而location.hash則可以用來獲取或設定頁面的標籤值。比如http://domain/#admin的location.hash=”#admin”。利用這個屬性值可以做一個非常有意義的事情。簡單來說就是”#”號後面跟著的內容,類似於查詢字串。
window.location.hash這個屬性可讀可寫。讀取時,可以用來判斷網頁狀態是否改變;寫入時,則會在不過載網頁的前提下,創造一條訪問歷史記錄。
-
Authorization Code Authorization Grant(簡寫AC)
之前介紹的IMP存在兩個問題,其一,授權伺服器沒有對客戶端應用進行認證,因為獲取Access Token的請求只提供了客戶端應用的ClientID而沒有ClientSecret;其二,Access Token是授權伺服器單獨頒發給客戶端應用的,應該對於其他人是不可見(包括擁有被訪問資源的授權者)。IMP型別授權的客戶端執行於純客戶端上下文環境,AC型別的使使用者執行於伺服器的應用,比如MVC應用中的Controller。
步驟1:客戶端向授權伺服器傳送一個獲取Authentication Code(認購權證)的請求,請求的地址和引數和IMP相似。
引數名 | 解釋 |
Response_type | 表示請求希望獲取的物件型別,在此我們希望獲取的是Authorization Code |
Redirect_uri | 表示授權伺服器在獲得使用者授權並完成對使用者認證後重定向的地址,AC就是以查詢字串方式附加 |
Client_id | 授權客戶端應用的clint_id |
Scope | 表示授權的範圍,根據具體需要的許可權集而定 |
步驟2:客戶端利用AC向授權伺服器獲取Access Token,一般為POST請求,引數包括:
引數名 | 解釋 |
Client | 授權客戶端應用的clint_id |
Client_secret | 該標識對應的ClientSecret |
Redirect_uri | 之前獲取AC時指定的重定向地址 |
Grant_type | 採用Authorization Grant型別,值為”authorization_code” |
授權伺服器接受到請求後,除了利用clientID和Secret對客戶端實施驗證外,還會檢查重定向地址是否一致,完成後,生成一個AccessToken發還。訊息包括:token_type,bearer;expires_in,3600;scope,wl.signin wl.basic;access_token;authentication_token。
出於安全考慮,access token有一個過期時限,此外授權伺服器還會返回一個長期有效的安全令牌,當ac token過期時,可以利用它再獲取,使用它需要在scope中加入”wl.offline_access”,相關程式碼如下所示。
對了,實際使用中,不需要這麼麻煩,你可以看到在project的app_start的StartupAuth中,可以看到微軟的設定,只用輸入對應的clientId和clientSecret。
Tip:
一個問題,為什麼我設定的www.sory.com可以訪問到,而我並未申請該域名和繫結IP?
WebAPI採用REST風格,將瀏覽器作為執行上下文客戶端js應用是主要消費者,但”同源策略”限制了js的跨站點呼叫,這將導致WebAPI不能跨域訪問資源,那麼它將”名不副實”,如何解決這個問題呢?將在後面意義道來,主要有jsonP和cros規範兩種方式。
瀏覽器作為internet工具,它為客戶端應用提供一個寄宿和執行的環境。這個應用就是javaScript程式,由於js指令碼並非都值得信奈,所以對js執行限制在一個沙盒sandbox中。同源策略是一項最基本的安全策略,是瀏覽器安全的基礎,它限制了來自A站點的指令碼只能操作A的頁面的DOM,跨域操作B站點的資源將會被拒絕。同源要求一下3方面相同:主機名稱(域名/子域名或者IP地址);埠號;網路協議(Schema)。需要注意的,對於一段js指令碼來說,其”源”與它儲存的地址無關,而取決於指令碼被載入的頁面,JSONP就利用了這個特性。除了<script>標籤,html還有其他一些具有src屬性的標籤(<img><iframe><link>)均具有跨域載入資源的能力,對於這些標籤,每次載入都涉及一個GET請求。同源策略主要針對Ajax請求,該策略主要限制了通過XMLHttpRequest傳送的Ajax請求,如果是一個異源地址,瀏覽器將拒絕讀取返回的內容。
一個跨域訪問的小例子,一個MVC的應用去呼叫一個webAPI應用的服務,兩者在不同的介面下時。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<head> <title>聯絡人列表</title> <script src="@Url.Content("~/scripts/jquery-1.10.2.js")"></script> </head> <body> <ul> id="contacts"</ul> <script type="text/javascript"> $(function () { var url = "http://www.sory.com/Sory.Entertainment.WebAPI/api/contact"; $.getJSON(url, null, function (contacts) { $.each(contacts, function (index, contact) { var html = "<li><ul>"; html += "<li>Name: " + contact.Name + "</li>"; html += "<li>Phone No: " + contact.PhoneNo + "</li>"; html += "<li>Email Address: " + contact.EmailAddress + "</li>"; html += "</ul>"; $("#contacts").append($(html)); }); }); }); </script> </body> |
跨域呼叫的錯誤資訊:XMLHttpRequest cannot load http://www.sory.com/Sory.Entertainment.WebAPI/api/contact. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:26829’ is therefore not allowed access.
-
JSONP方式
之前說過,js指令碼的源有載入頁面決定,而不是儲存地址。對於一段<script>標籤src屬性加在的js指令碼,它與當前頁面同源。對於之前的例子來說,可以將聯絡人列表的呈現單獨定義在listContacts函式中,並將WebAPI的地址置於<script>標籤的src屬性中來間接呼叫。
名稱空間:System.Net.Http,簡化了我們原有的HttpWebRequest的呼叫模式。
基本使用:
HttpResponseMessage response = client.GetAsync(“http://…”).Result;
Cw(response.Content.ReadAsAsync().Result.ToString();(這兒的result與await很類似啊, Task)
-
利用httpClient呼叫Basic認證下的WebAPI
這兒需要注意,首先在nuget中獲取httpClient相關元件,其依賴於Microsoft.Bcl庫,部分擴充套件方法需要新增相應名稱空間,自己找了半天ReadAsync這個泛型方法半天沒找到,也可以自己寫一個,比較簡單方便。
1 2 3 4 5 6 7 8 9 |
using (var client = new HttpClient()) { var identityString = string.Format("{0}:{1}", "cn1\\she_s", "Wanliwang11"); byte[] credential = Encoding.Default.GetBytes(identityString); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(credential)); HttpResponseMessage response = client.GetAsync("http://localhost/Sory.Entertainment.WebAPI/json/Demo/GetUsers").Result; var userNames = response.Content.ReadAsStringAsync().Result; return userNames; } |
-
利用httpClient呼叫forms認證下的WebAPI
在Froms認證時,我們首先需要請求login頁面,將使用者名稱密碼作為token傳送給伺服器,之後獲取伺服器響應資訊head中的”Set-Cookie”屬性,接著獲取其中key為”.ASPXAUTH”的cookie資訊,這個也就是伺服器端和客戶端通訊的token。之後再在自己真正需要的請求中,附加上該token(用FormUrlEncodeContent打包,這個類實際氣的就是原來mediaType=”application/x-www-form-urlencoded”的作用),還有要注意的是httpClientHandler中需要初始化CookieContainer。
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 |
public static string GetData() { var result = string.Empty; string token = GetSecurityToken("xionger", "123456", _loginUrl, ".ASPXAUTH"); if (!string.IsNullOrEmpty(token)) { var handler = new HttpClientHandler() { CookieContainer = new CookieContainer() }; 8 handler.CookieContainer.Add(new Uri(_targetUrl), new Cookie(".ASPXAUTH", token)); using (var client = new HttpClient(handler)) { var response = client.GetAsync(_targetUrl).Result; result = response.Content.ReadAsStringAsync().Result; } } return result; } private static string GetSecurityToken(string userName, string password, string url, string cookieName) { using (var client = new HttpClient()) { Dictionary<string, string> credential = new Dictionary<string, string>(); credential.Add("UserName", userName); credential.Add("Password", password); var response = client.PostAsync(url, new FormUrlEncodedContent(credential)).Result; IEnumerable<string> cookies; if (response.Headers.TryGetValues("Set-Cookie", out cookies)) { string token = cookies.FirstOrDefault(value => value.StartsWith(cookieName)); if (null == token) { return null; } return token.Split(';')[0].Substring(cookieName.Length + 1); } return null; } } |
參考資料:
-
蔣金楠. ASP.NET Web API 2框架揭祕[M]. 北京:電子工業出版社, 2014.
-
(美)加洛韋. ASP.NET MVC 5高階程式設計(第5版)[M]. 北京:清華大學出版社, 2015.