OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究_第1頁
OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究_第2頁
OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究_第3頁
OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究_第4頁
OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究_第5頁
已閱讀5頁,還剩30頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

/在上篇文章中我研究了OpenId及Dot\o"Net"NetOpenAuth的相關(guān)應(yīng)用,這一篇繼續(xù)研究OAuth2.

一.什么是OAuth2

OAuth是一種開放認(rèn)證協(xié)議,允許\o"用戶"用戶讓第三方應(yīng)用訪問該用戶在某一\o"網(wǎng)站"網(wǎng)站上存儲的私密的\o"資源"資源(如照片,\o"視頻"視頻,聯(lián)系人列表),而無需將用戶名和密碼提供應(yīng)第三方應(yīng)用.數(shù)字2表示現(xiàn)在使用第2代協(xié)議.

二.OAuth2中的角色

OAuth2有四種角色

resourceowner資源所有者:比方twitter用戶,他在twitter的\o"數(shù)據(jù)"數(shù)據(jù)就是資源,他自己就是這些資源的所有者。

resourceserver資源\o"效勞器"效勞器:保存資源的效勞器,別人要訪問受限制的資源就要出示AccessToken(訪問令牌)。

client\o"客戶端"客戶端:一個經(jīng)過授權(quán)后,可以代表資源所有者訪問資源效勞器上受限制資源的一方。比方

\o"開發(fā)者"開發(fā)者開發(fā)的應(yīng)用。

authorizationserver授權(quán)效勞器:對資源所有者進行認(rèn)證,認(rèn)證通過后,向客戶端發(fā)放AccessToken(訪問令牌)。

三.認(rèn)證過程

用戶訪問客戶端的網(wǎng)站,想操作自己存放在資源效勞提供方的資源。

客戶端將用戶引導(dǎo)至授權(quán)效勞提供方的授權(quán)頁面請求用戶授權(quán),在這個過程中將客戶端的回調(diào)連接發(fā)送給授權(quán)效勞提供方。

用戶在授權(quán)效勞提供方的網(wǎng)頁上輸入用戶名和密碼,然后授權(quán)該客戶端訪問所請求的資源。

授權(quán)成功后,授權(quán)效勞提供方對客戶端授予一個授權(quán)碼,網(wǎng)站跳回至客戶端。

客戶端獲得授權(quán)碼后,再次從授權(quán)效勞提供方請求獲取訪問令牌。

授權(quán)效勞提供方根據(jù)授權(quán)碼授予客戶端訪問令牌。

客戶端使用獲取的訪問令牌訪問存放在資源效勞提供方上的受保護的資源。

四.獲取訪問令牌方式

從上面可以看到,令牌是串起整個認(rèn)證流程的核心.OAuth2有四種獲取令牌的方式

AuthorizationCode授權(quán)碼方式:這種是推薦使用的,也是最平安的.

ImplicitGrant隱式授權(quán):相比授權(quán)碼授權(quán),隱式授權(quán)少了第一步的取AuthorizationCode的過程,而且不會返回refresh_token。主要用于無效勞器端的應(yīng)用,比方

\o"瀏覽器"瀏覽器插件。

ResourceOwnerPasswordCredentials資源所有者密碼證書授權(quán):這種驗證主要用于資源所有者對Client有極高的信任度的情況,比方操作\o"系統(tǒng)"系統(tǒng)或高權(quán)限程序。只有在不能使用其它授權(quán)方式的情況下才使用這種方式。

ClientCredentials客戶端證書授權(quán):這種情況下Client使用自己的client證書(如client_id及client_secret組成的httpbasic\o"驗證碼"驗證碼)來獲取accesstoken,只能用于信任的client。

本文主要講解第一種獲取方式.

有能有些人有這樣的疑問,為什么授權(quán)成功后不直接返回訪問令牌,則是獲取授權(quán)碼,然后使用授權(quán)碼去換訪問令牌.這個問題的答案在官方的文檔里,原因主要是保障數(shù)據(jù)平安性.當(dāng)用戶授權(quán)成功,瀏覽器從授權(quán)效勞器返回客戶端時,數(shù)據(jù)是通過QueryString傳遞的.如果直接返回訪問令牌,則直接在地址欄可見,相關(guān)的日志系統(tǒng)也會記錄,這會提高令牌被破解的風(fēng)險.返回授權(quán)碼,然后客戶端通過直接通信使用授權(quán)碼換取訪問令牌,整個過程對用戶是不可見的,這樣大大提高了平安性.

五.DotNetOpenAuth在OAuth2中的應(yīng)用

官方Sample內(nèi)包含有OAuth的完整例如,其授權(quán)效勞器使用Mvc編寫,客戶端與資源效勞器使用WebForm編寫,數(shù)據(jù)層使用了EF.為了更加貼進實際使用,減少無關(guān)雜音,本人模仿其重寫了一個Sample,本文的講解將圍繞自行編寫的Sample展開.Sample例如可于文后下載.

1.客戶端

客戶端\o"編程"編程主要圍繞三個類展開

AuthorizationServer\o"Description"Description,顧名思義,用于對效勞端的描述.如下所示privatestaticAuthorizationServerDescriptionAuthServerDescription;privatestaticreadonlyWebServerClientClient;staticOAuth2Client(){AuthServerDescription=newAuthorizationServerDescription();AuthServerDescription.TokenEndpoint=newUri("");AuthServerDescription.AuthorizationEndpoint=newUri("");Client=newWebServerClient(AuthServerDescription,"sampleconsumer","samplesecret");}

可以看到,主要設(shè)置其兩個地址:令牌獲取地址與授權(quán)地址.然后將其作為參數(shù)來構(gòu)建WebServerClient類.

WebServerClient類,是OAuth2的客戶端代理類,與授權(quán)效勞器和資源效勞器交互的方法都定義在上面.在實例化時需要傳入AuthServerDescr\o"ip"iption對象,客戶端名與客戶端密碼.這對名稱與密碼應(yīng)該是事先向授權(quán)效勞器申請的,用于標(biāo)識每一個使用數(shù)據(jù)的客戶端.各個客戶端擁有各自的名稱與密碼.

生成客戶端代理后,第一件事就是應(yīng)該訪問授權(quán)效勞器獲取授權(quán)碼.這主要由WebServerClient類的RequestUserAuthorization方法完成.

publicvoidRequestUserAuthorization(IEnumerable<string>scope=null,UrireturnTo=null);

在申請授權(quán)碼時,還會向授權(quán)效勞器發(fā)送申請權(quán)限的范圍,參數(shù)名叫scope.一般都是一個Url地址.

申請成功,授權(quán)效勞器返回后,客戶端需再次訪問授權(quán)效勞器申請訪問令牌.這主要由WebServerClient類的ProcessUserAuthorization方法完成publicIAuthorizationStateProcessUserAuthorization(HttpRequestBaserequest=null);

成功申請后,會返回一個IAuthorizationState接口對象,其定義如下stringAccessToken{get;set;}DateTime?AccessTokenExpirationUtc{get;set;}DateTime?AccessTokenIssueDateUtc{get;set;}UriCallback{get;set;}stringRefreshToken{get;set;}HashSet<string>Scope{get;}

很好理解,AccessToken為訪問令牌,RefreshToken為刷新令牌,AccessTokenIssueDateUtc為訪問令牌生成時間,AccessTokenExpirationUtc為訪問令牌過期時間,Callback為回調(diào)的Url,Scope為權(quán)限的范圍,或者叫被授權(quán)可以訪問的地址范圍.

在Sample中為了簡化編程對框架作了二次封裝,如下

1privatestaticAuthorizationServerDescriptionAuthServerDescription;23privatestaticreadonlyWebServerClientClient;45staticOAuth2Client()6{7AuthServerDescription=newAuthorizationServerDescription();8AuthServerDescription.TokenEndpoint=newUri("");9AuthServerDescription.AuthorizationEndpoint=newUri("");1011Client=newWebServerClient(AuthServerDescription,"sampleconsumer","samplesecret");12}1314privatestaticIAuthorizationStateAuthorization15{16get{return(AuthorizationState)HttpContext.Current.Session["Authorization"];}17set{HttpContext.Current.Session["Authorization"]=value;}18}1920publicstaticvoidGetUserAuthorization(stringscope)21{22GetUserAuthorization(newstring[]{scope});23}2425publicstaticvoidGetUserAuthorization(IEnumerable<string>scopes)26{27if(Authorization!=null)28{29return;30}3132IAuthorizationStateauthorization=Client.ProcessUserAuthorization();33if(authorization==null)34{35Client.RequestUserAuthorization(scopes);3637return;38}3940Authorization=authorization;41HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Path);42}

前12行為對象初始化,14到18行將獲取的權(quán)限對象保存在Session中,屬性名為Authorization.客戶端使用GetUserAuthorization方法來獲取對某地址訪問授權(quán).

在頁面中調(diào)用代碼如下if(!IsPostBack){OAuth2Client.GetUserAuthorization("");}

翻開頁面,首次調(diào)用GetUserAuthorization方法后,首先判斷權(quán)限對象Authorization是否為空.不為空說明已獲取到權(quán)限.為空則執(zhí)行ProcessUserAuthorization方法獲取訪問令牌,由于此時沒有授權(quán)碼,則返回的權(quán)限對象為空.最后通過RequestUserAuthorization方法向授權(quán)效勞器申請授權(quán)碼.

獲取成功后,瀏覽器頁面會刷新,在頁面地址后追加了授權(quán)碼.此時第二次執(zhí)行GetUserAuthorization方法.權(quán)限對象Authorization仍然為空,但由于已有授權(quán)碼,則ProcessUserAuthorization方法將向授權(quán)效勞器申請訪問令牌.獲取成功后將返回的權(quán)限對象賦給Authorization屬性,然后再次刷新本頁面.注意,刷新地址使用的是HttpContext.Current.Request.Path,而此屬性是不包括QueryString的.作用是將授權(quán)碼從地址欄中去除.

第三次執(zhí)行GetUserAuthorization方法,由于權(quán)限對象Authorization已不為空,則直接返回.

訪問令牌默認(rèn)是有時效的.當(dāng)過期后,要么走上面三步重新申請一個令牌,不過更好的方法是使用刷新令牌刷新訪問令牌.這主要由WebServerClient類的RefreshAuthorization方法完成

publicboolRefreshAuthorization(IAuthorizationStateauthorization,TimeSpan?skipIfUsefulLifeExceeds=null);

使用訪問令牌的方式,是將令牌添加到訪問資源效勞器Http請求的頭上,這主要由WebServerClient類的AuthorizeRequest方法完成publicvoidAuthorizeRequest(HttpWebRequestrequest,IAuthorizationStateauthorization);publicvoidAuthorizeRequest(WebHeaderCollectionrequestHeaders,IAuthorizationStateauthorization);

在Sample中針對Wcf請求作了二次封裝,如下1publicstaticTReturnUseService<TService,TReturn>(Expression<Func<TService,TReturn>>operation)2{3if(Authorization.AccessTokenExpirationUtc.HasValue)4{5Client.RefreshAuthorization(Authorization,TimeSpan.FromMinutes(2));6}78TServicechannel=newChannelFactory<TService>("*").CreateChannel();9IClientChannelclient=(IClientChannel)channel;1011HttpWebRequesthttpRequest=(HttpWebRequest)WebRequest.Create(client.RemoteAddress.Uri);12ClientBase.AuthorizeRequest(httpRequest,Authorization.AccessToken);13HttpRequestMessagePropertyhttpDetails=newHttpRequestMessageProperty();14httpDetails.Headers[HttpRequestHeader.Authorization]=httpRequest.Headers[HttpRequestHeader.Authorization];1516using(OperationContextScopescope=newOperationContextScope(client))17{18OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name]=httpDetails;1920client.Open();21TReturnresult=operation.Compile().Invoke(channel);22try23{24client.Close();25}26catch27{28client.Abort();29throw;30}3132returnresult;33}34}

在請求一個Wcf前,首先判斷有效期.如果少于2分鐘則首先刷新訪問令牌.之后構(gòu)建一個HttpWebRequest對象,并使用AuthorizeRequest方法將訪問令牌添加在請求頭上.從第13行之后是Wcf的特定寫法,其中13到18行表示將Http授權(quán)頭賦給Wcf授權(quán)頭.

2.授權(quán)效勞端

效勞端要做的事其實很好理解,就是記錄某用戶在某客戶端的授權(quán)情況.其使用\o"數(shù)據(jù)庫"數(shù)據(jù)庫來保存相關(guān)信息.Client表存儲客戶端,User表存儲用戶,ClientAuthorization表是張關(guān)系表,存儲某用戶在某客戶端授予的權(quán)限.Nonce存儲訪問\o"隨機數(shù)"隨機數(shù),SymmertricCryptoKey表存儲對稱加密的密碼.

效勞端主要圍繞以下對象編程

AuthorizationServer類,代表授權(quán)效勞類.主要的功能都由它提供.IAuthorizationServerHost接口是編寫驗證邏輯的地方,由OAuth2AuthorizationServer類實現(xiàn),ICryptoKeyStore是訪問密碼的接口,INonceStore是訪問隨機數(shù)的地方,這兩個接口由DatabaseKeyNonceStore類實現(xiàn),IClientDescription是描述客戶端的接口,由Client實現(xiàn).

在本Sample中,OpenId與OAuth2是配合使用的.用戶需要先去OpenId進行登錄,然后去OAuth2進行授權(quán).從這個意義上講,OAuth2受OpenId的統(tǒng)一管理,是其一個客戶端.

AccountController是一個典型的OpenId客戶端編程.上篇文章已有講解,故不贅述.

當(dāng)客戶端申請授權(quán)碼時,首先執(zhí)行OAuthController類的Authorize方法,如下,有刪節(jié)publicActionResultAuthorize(){varpendingRequest=this.authorizationServer.ReadAuthorizationRequest();if((this.authorizationServer.AuthorizationServerServicesasOAuth2AuthorizationServer).CanBeAutoApproved(pendingRequest)){varapproval=this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest,HttpContext.User.Identity.Name);returnthis.authorizationServer.Channel.PrepareResponse(approval).AsActionResult();}database.AddParameter("@ClientIdentifier",pendingRequest.ClientIdentifier);ViewBag.Name=database.ExecuteScalar("selectnamefromClientwhereClientIdentifier=@ClientIdentifier").ToString();ViewBag.AuthorizationRequest=pendingRequest;returnView();}

AuthorizationServer類的ReadAuthorizationRequest方法會獲取用戶請求并返回一個EndUserAuthorizationRequest對象,此對象定義如下publicUriCallback{get;set;}publicstringClientIdentifier{get;set;}publicstringClientState{get;set;}publicvirtualEndUserAuthorizationResponseTypeResponseType{get;}publicHashSet<string>Scope{get;}

可以看到包括了客戶端的相關(guān)信息.然后將此對象傳入OAuth2AuthorizationServer對像的CanBeAuto\o"App"Approved方法,查看能否自動發(fā)放授權(quán)碼.publicboolCanBeAutoApproved(EndUserAuthorizationRequestauthorizationRequest){if(authorizationRequest.ResponseType==EndUserAuthorizationResponseType.AuthorizationCode){database.AddParameter("@ClientIdentifier",authorizationRequest.ClientIdentifier);objectresult=database.ExecuteScalar("selectClientSecretfromclientwhereClientIdentifier=@ClientIdentifier");if(result!=null&&!string.IsNullOrEmpty(result.ToString())){returnthis.IsAuthorizationValid(authorizationRequest.Scope,authorizationRequest.ClientIdentifier,DateTime.UtcNow,HttpContext.Current.User.Identity.Name);}}returnfalse;}

此方法是查找數(shù)據(jù)庫中有無此客戶端記錄且密碼不為空,如果不為空且處于獲取授權(quán)碼階段,則會調(diào)用了IsAuthorizationValid方法privateboolIsAuthorizationValid(HashSet<string>requestedScopes,stringclientIdentifier,DateTimeissuedUtc,stringusername){issuedUtc+=TimeSpan.FromSeconds(1);database.AddParameter("@ClientIdentifier",clientIdentifier);database.AddParameter("@CreatedOnUtc",issuedUtc);database.AddParameter("@ExpirationDateUtc",DateTime.UtcNow);database.AddParameter("@OpenIDClaimedIdentifier",username);StringBuildersb=newStringBuilder();sb.Append("selectscopefrom[user]u");sb.Append("joinClientAuthorizationcaonu.userid=ca.userid");sb.Append("joinClientconc.clientid=ca.clientid");sb.Append("wherec.ClientIdentifier=@ClientIdentifier");sb.Append("andCreatedOnUtc<=@CreatedOnUtc");sb.Append("and(ExpirationDateUtcisnullorExpirationDateUtc>=@ExpirationDateUtc)");sb.Append("andu.OpenIDClaimedIdentifier=@OpenIDClaimedIdentifier");DataTabledt=database.ExecuteDataSet(sb.ToString()).Tables[0];if(dt.Rows.Count==0){returnfalse;}vargrantedScopes=newHashSet<string>(OAuthUtilities.ScopeStringComparer);foreach(DataRowdrindt.Rows){grantedScopes.UnionWith(OAuthUtilities.SplitScopes(dr["scope"].ToString()));}returnrequestedScopes.IsSubsetOf(grantedScopes);}

可以看到,此方法查找指定用戶在指定客戶端上是否有對目標(biāo)范圍的授權(quán),且沒有過期.也就是說,如果客服端的密碼不能為空,且當(dāng)前用戶在此客戶端上對目標(biāo)范圍還有未過期的授權(quán),則自動發(fā)放授權(quán)碼.

回到最初的Authorize方法.如果可以自動發(fā)放授權(quán)碼,則調(diào)用AuthorizationServer類的PrepareApproveAuthorizationRequest方法生成一個授權(quán)碼,并通過AuthorizationServer類Channel屬性的PrepareResponse方法最終返回給客戶端.

如果不能自動發(fā)放,則瀏覽器會跳轉(zhuǎn)到一個確認(rèn)頁面,如下列圖所示

點擊后執(zhí)行OAuthController類的AuthorizeResponse方法,有刪節(jié).publicActionResultAuthorizeResponse(boolisApproved){varpendingRequest=this.authorizationServer.ReadAuthorizationRequest();IDirectedProtocolMessageresponse;if(isApproved){database.AddParameter("@ClientIdentifier",pendingRequest.ClientIdentifier);intclientId=Convert.ToInt32(database.ExecuteScalar("selectclientIdfromclientwhereClientIdentifier=@ClientIdentifier"));database.AddParameter("@OpenIDClaimedIdentifier",User.Identity.Name);intuserId=Convert.ToInt32(database.ExecuteScalar("selectuserIdfrom[user]whereOpenIDClaimedIdentifier=@OpenIDClaimedIdentifier"));database.AddParameter("@CreatedOnUtc",DateTime.UtcNow);database.AddParameter("@clientId",clientId);database.AddParameter("@userId",userId);database.AddParameter("@Scope",OAuthUtilities.JoinScopes(pendingRequest.Scope));database.ExecuteNonQuery("insertintoClientAuthorizationvalues(null,@CreatedOnUtc,@clientId,@userId,@Scope,null)");response=this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest,User.Identity.Name);}else{response=this.authorizationServer.PrepareRejectAuthorizationRequest(pendingRequest);}returnthis.authorizationServer.Channel.PrepareResponse(response).AsActionResult();}

邏輯比較簡單,如果同意,則獲取客戶端信息后,在數(shù)據(jù)庫的ClientAuthorization表中插入某時某用戶在某客戶端對于某訪問范圍的權(quán)限信息,然后如同上面一樣,調(diào)用AuthorizationServer類的PrepareApproveAuthorizationRequest方法生成一個授權(quán)碼,并通過AuthorizationServer類Channel屬性的PrepareResponse方法最終返回給客戶端.

有一點需要注意.Authorize方法是從請求中獲取客戶端信息,而AuthorizeResponse方法則是從Authorize方法所對應(yīng)的View中獲取客戶端信息.所以此View必需包含相關(guān)系統(tǒng).在Sample中我首先將獲取出來的pendingRequest對象賦于ViewBag.AuthorizationRequest,然后在View中將其放入隱藏域.注意,其名字是固定的.

@{ViewBag.Title="Authorize";Layout="~/Views/Shared/_Layout.cshtml";DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationRequestAuthorizationRequest=ViewBag.AuthorizationRequest;}<h2>Authorize</h2>是否授權(quán)@ViewBag.Name訪問以下地址<hr/>@foreach(stringscopeinAuthorizationRequest.Scope){@scope<br/>}@using(Html.BeginForm("AuthorizeResponse","OAuth")){@Html.AntiForgeryToken()@Html.Hidden("isApproved")@Html.Hidden("client_id",AuthorizationRequest.ClientIdentifier)@Html.Hidden("redirect_uri",AuthorizationRequest.Callback)@Html.Hidden("state",AuthorizationRequest.ClientState)@Html.Hidden("scope",DotNetOpenAuth.OAuth2.OAuthUtilities.JoinScopes(AuthorizationRequest.Scope))@Html.Hidden("response_type",AuthorizationRequest.ResponseType==DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationResponseType.AccessToken?"token":"code")<div><inputtype="submit"value="Yes"onclick="document.getElementById('isApproved').value=true;returntrue;"/><inputtype="submit"value="No"onclick="document.getElementById('isApproved').value=false;returntrue;"/></div>}

此時客戶端已獲取到授權(quán)碼.然后會發(fā)出第二次請求申請訪問令牌.這個請求由OAuthController類的Token方法處理publicActionResultToken(){returnthis.authorizationServer.HandleTokenRequest(this.Request).AsActionResult();}

實際上由AuthorizationServer類的HandleTokenRequest方法處理,最終調(diào)用OAuth2AuthorizationServer類的CreateAccessToken方法創(chuàng)立訪問令牌并返回客戶端.

大體的效勞端編程接口分析到此結(jié)束,下面我們深入源碼來理解這些關(guān)鍵類的\o"架構(gòu)"架構(gòu)模式.

AuthorizationServer類主要提供編程接口,而自行實現(xiàn)的OAuth2AuthorizationServer類,DatabaseKeyNonceStore類和Client類則主要負(fù)責(zé)與數(shù)據(jù)庫的交互.真正負(fù)責(zé)通信的是Channel抽象類,其作為AuthorizationServer類的Channel屬性對外公布,具體實現(xiàn)類為OAuth2AuthorizationServerChannel類.

在Channel類上作者使用了一種類似于Asp.Net的管道模型的方式來架構(gòu)此類.相對于IHttpModule接口,這里的接口名叫IChannelBindingElement.其定義如下publicinterfaceIChannelBindingElement{ChannelChannel{get;set;}MessageProtectionsProtection{get;}MessageProtections?ProcessOutgoingMessage(IProtocolMessagemessage);MessageProtections?ProcessIncomingMessage(IProtocolMessagemessage);}

而在Channel類中的關(guān)鍵局部如下privatereadonlyList<IChannelBindingElement>outgoingBindingElements=newList<IChannelBindingElement>();privatereadonlyList<IChannelBindingElement>incomingBindingElements=newList<IChannelBindingElement>();protectedChannel(IMessageFactorymessageTypeProvider,paramsIChannelBindingElement[]bindingElements){...this.outgoingBindingElements=newList<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));this.incomingBindingElements=newList<IChannelBindingElement>(this.outgoingBindingElements);this.incomingBindingElements.Reverse();...}protectedvirtualvoidProcessIncomingMessage(IProtocolMessagemessage){foreach(IChannelBindingElementbindingElementinthis.IncomingBindingElements){...

MessageProtections?elementProtection=bindingElement.ProcessIncomingMessage(message);

...

}...}protectedvoidProcessOutgoingMessage(IProtocolMessagemessage){foreach(IChannelBindingElementbindingElementinthis.outgoingBindingElements){...

MessageProtections?elementProtection=bindingElement.ProcessOutgoingMessage(message);

...}...}

可以看到定義了兩個集合分別存儲請求過濾器與響應(yīng)過濾器.兩者都由構(gòu)造函數(shù)初始化,內(nèi)容一樣,順序相反.在讀取請求時會遍歷In\o"com"comingBindingElements集合并逐個調(diào)用ProcessIncomingMessage方法對傳入的message進行處理,在發(fā)出響應(yīng)時會遍歷outgoingBindingElements集合并逐個調(diào)用ProcessOutgoingMessage方法對message進行處理.

下面就以授權(quán)效勞器接收授權(quán)碼并發(fā)送訪問令牌為例來分析此架構(gòu)在實例中的應(yīng)用.

上面講過,客戶端發(fā)送請求后,由OAuthController類的Token方法響應(yīng)publicActionResultToken(){returnthis.authorizationServer.HandleTokenRequest(this.Request).AsActionResult();}

調(diào)用了AuthorizationServer類的HandleTokenRequest方法,有刪節(jié)publicOutgoingWebResponseHandleTokenRequest(HttpRequestBaserequest=null){try{if(this.Channel.TryReadFromRequest(request,outrequestMessage)){varaccessTokenResult=this.AuthorizationServerServices.CreateAccessToken(requestMessage);...}...}...returnthis.Channel.PrepareResponse(responseMessage);}

可以看到,實際都用調(diào)用Channel中的方法,讀取請求調(diào)用的TryReadFromRequest方法publicboolTryReadFromRequest<TRequest>(HttpRequestBasehttpRequest,outTRequestrequest)whereTRequest:class,IProtocolMessage{...IProtocolMessageuntypedRequest=this.ReadFromRequest(httpRequest);...}

之后調(diào)用了自身的ReadFromRequest方法publicIDirectedProtocolMessageReadFromRequest(HttpRequestBasehttpRequest){IDirectedProtocolMessagerequestMessage=this.ReadFromRequestCore(httpRequest);if(requestMessage!=null){vardirectRequest=requestMessageasIHttpDirectRequest;if(directRequest!=null){foreach(stringheaderinhttpRequest.Headers){directRequest.Headers[header]=httpRequest.Headers[header];}}this.ProcessIncomingMessage(requestMessage);}returnrequestMessage;}

可以看到,這里就會調(diào)用ProcessIncomingMessage方法對通過ReadFromRequestCore方法讀取到請求作過濾

回到HandleTokenRequest方法,當(dāng)其調(diào)用AuthorizationServerServices屬性的CreateAccessToken方法生成訪問令牌后,會調(diào)用Channel屬性的PrepareResponse方法生成響應(yīng)publicOutgoingWebResponsePrepareResponse(IProtocolMessagemessage){...this.ProcessOutgoingMessage(message);...OutgoingWebResponseresult;switch(message.Transport){caseMessageTransport.Direct:result=this.PrepareDirectResponse(message);break;...}result.Headers[HttpResponseHeader.CacheControl]="no-cache,no-store,max-age=0,must-revalidate";result.Headers[HttpResponseHeader.Pragma]="no-cache";returnresult;}

可以看到,首先就調(diào)用了ProcessOutgoingMessage方法過濾響應(yīng),然后調(diào)用PrepareDirectResponse方法最終生成響應(yīng)

下面繼續(xù)分析其過濾器組件的實現(xiàn).

我們在使用AuthorizationServer類時,其Channel屬性的實際類型是OAuth2AuthorizationServerChannel類.此類的構(gòu)造函數(shù)會調(diào)用本類InitializeBindingElements靜態(tài)方法加載兩個IChannelBindingElement類型的過濾器,然后傳入父類構(gòu)造函數(shù),最終會被添加到上文所說的Channel類的outgoingBindingElements集合與incomingBindingElements集合中.protectedinternalOAuth2AuthorizationServerChannel(IAuthorizationServerHostauthorizationServer,ClientAuthenticationModuleclientAuthenticationModule):base(MessageTypes,InitializeBindingElements(authorizationServer,clientAuthenticationModule)){Requires.NotNull(authorizationServer,"authorizationServer");this.AuthorizationServer=authorizationServer;}privatestaticIChannelBindingElement[]InitializeBindingElements(IAuthorizationServerHostauthorizationServer,ClientAuthenticationModuleclientAuthenticationModule){...varbindingElements=newList<IChannelBindingElement>();bindingElements.Add(newMessageValidationBindingElement(clientAuthenticationModule));bindingElements.Add(newTokenCodeSerializationBindingElement());returnbindingElements.ToArray();}

從功能上講,MessageValidationBindingElement負(fù)責(zé)驗證,TokenCodeSerializationBindingElement負(fù)責(zé)加解密,數(shù)字簽名,請求保護等,從順序上講,讀取請求時先執(zhí)行后者再執(zhí)行前者,發(fā)送響應(yīng)時反之.

首先查看MessageValidationBindingElement類privatereadonlyClientAuthenticationModuleclientAuthenticationModule;internalMessageValidationBindingElement(ClientAuthenticationModuleclientAuthenticationModule){this.clientAuthenticationModule=clientAuthenticationModule;}publicoverrideMessageProtections?ProcessIncomingMessage(IProtocolMessagemessage){...if(authenticatedClientRequest!=null){...varresult=this.clientAuthenticationModule.TryAuthenticateClient(this.AuthServerChannel.AuthorizationServer,authenticatedClientRequest,outclientIdentifier);...}...}

即然是驗證客戶端,那么只需要在讀取請求時執(zhí)行即可,可以看到此類將實際驗證又委托給了ClientAuthenticationModule類的TryAuthenticateClient方法.

publicabstractclassClientAuthenticationModule{publicabstractClientAuthenticationResultTryAuthenticateClient(IAuthorizationServerHostauthorizationServerHost,AuthenticatedClientRequestBaserequestMessage,outstringclientIdentifier);protectedstaticClientAuthenticationResultTryAuthenticateClientBySecret(IAuthorizationServerHostauthorizationServerHost,stringclientIdentifier,stringclientSecret){...

}}

可以看到此類是個抽象類.在實際中真正執(zhí)行的是ClientCredentialHttpBasicReader類與ClientCredentialMessagePartReader類,各自重寫的TryAuthenticateClient方法其際調(diào)用的都是基類的TryAuthenticateClientBySecret靜態(tài)方法.publicclassClientCredentialHttpBasicReader:ClientAuthenticationModule{publicoverrideClientAuthenticationResultTryAuthenticateClient(IAuthorizationServerHostauthorizationServerHost,AuthenticatedClientRequestBaserequestMessage,outstringclientIdentifier){...varcredential=OAuthUtilities.ParseHttpBasicAuth(requestMessage.Headers);if(credential!=null){clientIdentifier=credential.UserName;returnTryAuthenticateClientBySecret(authorizationServerHost,credential.UserName,credential.Password);}clientIdentifier=null;returnClientAuthenticationResult.NoAuthenticationRecognized;}}publicclassClientCredentialMessagePartReader:ClientAuthenticationModule{publicoverrideClientAuthenticationResultTryAuthenticateClient(IAuthorizationServerHostauthorizationServerHost,AuthenticatedClientRequestBaserequestMessage,outstringclientIdentifier){...clientIdentifier=requestMessage.ClientIdentifier;returnTryAuthenticateClientBySecret(authorizationServerHost,requestMessage.ClientIdentifier,requestMessage.ClientSecret);}}

有意思的是,在實際使用中實現(xiàn)了InitializeBindingElements接口的MessageValidationBindingElement類并不直接調(diào)用實現(xiàn)了ClientAuthenticationModule抽象類的上面的兩者,而是在中間又參加了一個AggregatingClientCredentialReader類,有點像代理模式,整個邏輯的關(guān)鍵代碼如下,有刪節(jié)publicclassAuthorizationServer{privatereadonlyList<ClientAuthenticationModule>clientAuthenticationModules=newList<ClientAuthenticationModule>();privatereadonlyClientAuthenticationModuleaggregatingClientAuthenticationModule;publicAuthorizationServer(IAuthorizationServerHostauthorizationServer){this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true));this.aggregatingClientAuthenticationModule=newAggregatingClientCredentialReader(this.clientAuthenticationModules);this.Channel=newOAuth2AuthorizationServerChannel(authorizationServer,this.aggregatingClientAuthenticationModule);...}}internalclassOAuth2AuthorizationServerSection:ConfigurationSection{privatestaticreadonlyTypeConfigurationCollection<ClientAuthenticationModule>defaultClientAuthenticationModules=newTypeConfigurationCollection<ClientAuthenticationModule>(newType[]{typeof(ClientCredentialHttpBasicReader),typeof(ClientCredentialMessagePartReader)});internalstaticOAuth2AuthorizationServerSectionConfiguration{get{return(OAuth2AuthorizationServerSection)ConfigurationManager.GetSection(SectionName)??newOAuth2AuthorizationServerSection();}}internalTypeConfigurationCollection<ClientAuthenticationModule>ClientAuthenticationModules{get{varconfigResult=(TypeConfigurationCollection<ClientAuthenticationModule>)this[ClientAuthenticationModulesElementName];returnconfigResult!=null&&configResult.Count>0?configResult:defaultClientAuthenticationModules;}...}}

可以看到,在創(chuàng)立AuthorizationServer類時,就會從OAuth2AuthorizationServerSection類,也就是配置文件中獲取ClientAuthenticationModule類名.如果沒有任何配置,則使用默認(rèn)的ClientCredentialHttpBasicReader類與ClientCredentialMessagePartReader類.然后將獲取的ClientAuthenticationModule類集合作為參數(shù)創(chuàng)立AggregatingClientCredentialReader類,最后將AggregatingClientCredentialReader類實例作為參數(shù)傳入Channel中,就如上文所說,包裝為實現(xiàn)了InitializeBindingElements接口的MessageValidationBindingElement類.

上文說過了,MessageValidationBindingElement類只與ClientAuthenticationModule抽象類交互,所以AggregatingClientCredentialReader類也實現(xiàn)了ClientAuthenticationModule抽象類

internalclassAggregatingClientCredentialReader:ClientAuthenticationModule{privatereadonlyIEnumerable<ClientAuthenticationModule>authenticators;internalAggregatingClientCredentialReader(IEnumerable<ClientAuthenticationModule>authenticators){this.authenticators=authenticators;}publicoverrideClientAuthenticationResultTryAuthenticateClient(IAuthorizationServerHostauthorizationServerHost,AuthenticatedClientRequestBaserequestMessage,outstringclientIdentifier){...foreach(varcandidateAuthenticatorinthis.authenticators){stringcandidateClientIdentifier;varresultCandidate=candidateAuthenticator.TryAuthenticateClient(authorizationServerHost,requestMessage,outcandidateClientIdentifier);...}...}}

如上文所說,這很像一個代理代,其內(nèi)部保存了傳入的ClientAuthenticationModule類集合,并實現(xiàn)了ClientAuthenticationModule抽象類.調(diào)用抽象方法TryAuthenticateClient最終會轉(zhuǎn)變?yōu)楸闅vClientAuthenticationModule集合并逐個調(diào)用.

回到ClientAuthenticationModule類的靜態(tài)方法TryAuthenticateClientBySecret,這也是MessageValidationBindingElement類實現(xiàn)客戶端研究的核心方法protectedstaticClientAuthenticationResultTryAuthenticateClientBySecret(IAuthorizationServerHostauthorizationServerHost,stringclientIdentifier,stringclientSecret){if(!string.IsNullOrEmpty(clientIdentifier)){varclient=authorizationServerHost.GetClient(clientIdentifier);if(client!=null){if(!string.IsNullOrEmpty(clientSecret)){if(client.IsValidClientSecret(clientSecret)){...}}}}...}

可以看到,它實際上使用了我們自己寫的IAuthorizationServerHost接口實現(xiàn)類OAuth2AuthorizationServer,從數(shù)據(jù)庫中獲取相關(guān)信息驗證客戶端.首先調(diào)用GetClient方法查找客戶端,如果存在,則調(diào)用Client對象的IsValidClientSecret方法驗證密碼是否正確.

上文說過MessageValidationBindingElement類主要用來作驗證.除了調(diào)用ClientAuthenticationModule類驗證客戶名密碼外,還做了很多其它方面的驗證,比方客戶端的CallbackUrl是否合法與一致,這通過調(diào)用Client類的IsCallbackAllowed方法與DefaultCallback屬性完成.請求令牌的客戶端是否就是我們即將發(fā)送令牌的客戶端,客戶端請求的權(quán)限范圍沒有超出在授權(quán)效勞器申請的權(quán)限范圍,令牌還未被注銷或過期之類的.這實際上調(diào)用了OAuth2AuthorizationServer類的IsAuthorizationValid方法.

下面來看一下TokenCodeSerializationBindingElement類

首先再回憶一下授權(quán)過程,客戶端第一次向授權(quán)效勞器發(fā)出請求,返回授權(quán)碼,然后客戶端第二次使用授權(quán)碼向授權(quán)效勞端發(fā)出請求,返回訪問令牌,如果客戶端需要刷新訪問令牌,則向授權(quán)效勞器發(fā)送刷新令牌,返回訪問令牌.這里有三個重要對象:授權(quán)碼,刷新令牌,訪問令牌.對于前兩者,授權(quán)效勞器是既可能接收也可能發(fā)送,對于最后者,只會發(fā)送不會接收.TokenCodeSerializationBindingElement類就是按這么來設(shè)計的.publicoverrideMessageProtections?ProcessOutgoingMessage(IProtocolMessagemessage){//Serializetheauthorizationcode,ifthereisone.varauthCodeCarrier=messageasIAuthorizationCodeCarryingRequest;if(authCodeCarrier!=null){varcodeFormatter=AuthorizationCode.CreateFormatter(this.AuthorizationServer);varcode=authCodeCarrier.AuthorizationDescription;authCodeCarrier.Code=codeFormatter.Serialize(code);returnMessageProtections.None;}//Serializetherefreshtoken,ifapplicable.varrefreshTokenResponse=messageasAccessTokenSuccessResponse;if(refreshTokenResponse!=null&&refreshTokenResponse.HasRefreshToken){varrefreshTokenCarrier=(IAuthorizationCarryingRequest)message;varrefreshToken=newRefreshToken(refreshTokenCarrier.AuthorizationDe

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論