【转】C#搭建Oauth2.0认证流程以及代码示例
對于一個普遍問題,必有對應(yīng)的一個簡潔優(yōu)美的解決方案。這也許只是一廂情愿,因為根據(jù)宇宙法則,所有事物總歸趨于混沌,而OAuth協(xié)議就是混沌中的產(chǎn)物,不管是1.0、1.0a還是2.0,單看版本號就讓人神傷。
OAuth2.0包含四種角色:
??? 用戶,又叫資源所有者
??? 客戶端,俗稱第三方商戶
??? 授權(quán)服務(wù)端,頒發(fā)AccessToken
??? 資源服務(wù)端,根據(jù)AccessToken開放相應(yīng)的資源訪問權(quán)限
?本文涉及到三種授權(quán)模式:?
Authorization Code模式:這是現(xiàn)在互聯(lián)網(wǎng)應(yīng)用中最常見的授權(quán)模式。客戶端引導(dǎo)用戶在授權(quán)服務(wù)端輸入憑證獲取用戶授權(quán)(AccessToken),進而訪問用戶資源。需要注意的是,在用戶授權(quán)后,授權(quán)服務(wù)端先回傳客戶端授權(quán)碼,然后客戶端再使用授權(quán)碼換取AccessToken。為什么不直接返回AccessToken呢?主要是由于用戶授權(quán)后,授權(quán)服務(wù)端重定向到客戶端地址,此時數(shù)據(jù)只能通過QueryString方式向客戶端傳遞,在地址欄URL中可見,不安全,于是分成了兩步,第二步由客戶端主動請求獲取最終的令牌。
Client Credentials Flow:客戶端乃是授權(quán)服務(wù)端的信任合作方,不需要用戶參與授權(quán),事先就約定向其開放指定資源(不特定于用戶)的訪問權(quán)限。客戶端通過證書或密鑰(或其它約定形式)證明自己的身份,獲取AccessToken,用于后續(xù)訪問。
Username and Password Flow:客戶端被用戶和授權(quán)服務(wù)端高度信任,用戶直接在客戶端中輸入用戶名密碼,然后客戶端傳遞用戶名密碼至授權(quán)服務(wù)端獲取AccessToken,便可訪問相應(yīng)的用戶資源。這在內(nèi)部多系統(tǒng)資源共享、同源系統(tǒng)資源共享等場景下常用,比如單點登錄,在登錄時就獲取了其它系統(tǒng)的AccessToken,避免后續(xù)授權(quán),提高了用戶體驗。
?上述模式涉及到三類憑證:
AuthorizationCode:授權(quán)碼,授權(quán)服務(wù)端和客戶端之間傳輸。
AccessToken:訪問令牌,授權(quán)服務(wù)端發(fā)給客戶端,客戶端用它去到資源服務(wù)端請求資源。
RefreshToken:刷新令牌,授權(quán)服務(wù)端和客戶端之間傳輸。
?對客戶端來說,授權(quán)的過程就是獲取AccessToken的過程。
?
總的來說,OAuth并沒有新鮮玩意,仍是基于加密、證書諸如此類的技術(shù),在OAuth出來之前,這些東東就已經(jīng)被大伙玩的差不多了。OAuth給到我們的最大好處就是統(tǒng)一了流程標(biāo)準(zhǔn),一定程度上促進了互聯(lián)網(wǎng)的繁榮。
我接到任務(wù)后,本著善假于物的理念,先去網(wǎng)上搜了一遍,原本以為有很多資源,結(jié)果只搜到DotNetOpenAuth這個開源組件。更讓人失望的是,官方API文檔沒找到(可能是我找的姿勢不對,有知道的兄弟告知一聲),網(wǎng)上其它資料也少的可憐,其間發(fā)現(xiàn)一篇 OAuth2學(xué)習(xí)及DotNetOpenAuth部分源碼研究 ,欣喜若狂,粗粗瀏覽一遍,有收獲,卻覺得該組件未免過于繁雜(由于時間緊迫,我并沒有深入研究,只是當(dāng)前觀點)。DotNetOpenAuth包含OpenID、OAuth1.0[a]/2.0,自帶的例子有幾處暗坑,不易(能)調(diào)通。下面介紹我在搭建基于該組件的OAuth2.0授權(quán)框架時的一些心得體會。
本文介紹的DotNetOpenAuth乃是對應(yīng).Net4.0的版本。
授權(quán)服務(wù)端
授權(quán)服務(wù)端交道打的最多的就是客戶端,于是定義一個Client類,實現(xiàn)DotNetOpenAuth.OAuth2.IClientDescription接口,下面我們來看IClientDescription的定義:
public interface IClientDescription {
??? Uri DefaultCallback { get; }
??? //0:有secret 1:沒有secret
??? ClientType ClientType { get; }
??? //該client的secret是否為空
??? bool HasNonEmptySecret { get; }
??? //檢查傳入的callback與該client的callback是否一致
??? bool IsCallbackAllowed(Uri callback);
??? //檢查傳入的secret與該client的secret是否一致
??? bool IsValidClientSecret(string secret);
}
?
其中隱含了許多信息。DefaultCallback表示客戶端的默認回調(diào)地址(假如有的話),在接收客戶端請求時,使用IsCallbackAllowed判斷回調(diào)地址是否合法(比如查看該次回調(diào)地址和默認地址是否屬于同一個域),過濾其它應(yīng)用的惡意請求。若ClientType 為0,則表示客戶端需持密鑰(secret)表明自己的身份,授權(quán)服務(wù)端可以據(jù)此賦予此類客戶端相對更多的權(quán)限。自定義的Client類一般需要多定義一個ClientSecret屬性。DefaultCallback和ClientSecret在下文常有涉及。
?
DotNetOpenAuth預(yù)定義了一個接口——IAuthorizationServerHost,這是個重要的接口,定義如下:
public interface IAuthorizationServerHost
{
??? ICryptoKeyStore CryptoKeyStore { get; }
??? INonceStore NonceStore { get; }
??? AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest);
??? AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest);
??? AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage);
??? IClientDescription GetClient(string clientIdentifier);
??? bool IsAuthorizationValid(IAuthorizationDescription authorization);
}
?
簡單地說,CryptoKeyStore用于存取對稱加密密鑰,用于授權(quán)碼和刷新令牌的加密,由于客戶端不需要對它們進行解密,所以密鑰只存于授權(quán)服務(wù)端;關(guān)于AccessToken的傳輸則略有不同,關(guān)于這點我們待會說。理解NonceStore 屬性需要知道 N once和 Timestamp的概念,Nonce與消息合并加密可防止重放攻擊,Timestamp是為了避免可能的Nonce重復(fù)問題,也將一同參與加密,具體參看 nonce和timestamp在Http安全協(xié)議中的作用 ;這項技術(shù)放在這里主要是為了確保一個授權(quán)碼只能被使用一次。 CheckAuthorizeClientCredentialsGrant方法在客戶端憑證模式下使用,CheckAuthorizeResourceOwnerCredentialGrant在用戶名密碼模式下使用,經(jīng)測試,IsAuthorizationValid方法只在授權(quán)碼模式下被調(diào)用,這三個方法的返回值標(biāo)示是否通過授權(quán)。
?
當(dāng)授權(quán)通過后,通過CreateAccessToken生成AccessToken并返回給客戶端,客戶端于是就可以用AccessToken訪問資源服務(wù)端了。那當(dāng)資源服務(wù)端接收到AccessToken時,需要做什么工作呢?首先,它要確認這個AccessToken是由合法的授權(quán)服務(wù)端頒發(fā)的,否則,攻擊者就能使用DotNetOpenAuth另外建一個授權(quán)服務(wù)端,后果可想而知。說到身份認證,最成熟的就是RSA簽名技術(shù),即授權(quán)服務(wù)端私鑰對AccessToken簽名,資源服務(wù)端接收后使用授權(quán)服務(wù)端的公鑰驗證。我們還可以使用 資源服務(wù)器公/私鑰對來加解密AccessToken(簽名在加密后),這對于OAuth2.0來說沒任何意義,而是為OAuth1.0服務(wù)的。
public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
{
??? var accessToken = new AuthorizationServerAccessToken();
??? int minutes = 0;
??? string setting = ConfigurationManager.AppSettings["AccessTokenLifeTime"];
??? minutes = int.TryParse(setting, out minutes) ? minutes : 10;//10分鐘
??? accessToken.Lifetime = TimeSpan.FromMinutes(minutes);
??? //這里設(shè)置加密公鑰
??? //accessToken.ResourceServerEncryptionKey = new RSACryptoServiceProvider();
??? //accessToken.ResourceServerEncryptionKey.ImportParameters(ResourceServerEncryptionPublicKey);
??? //簽名私鑰,這是必須的(在后續(xù)版本中可以設(shè)置accessToken.SymmetricKeyStore替代)
??? accessToken.AccessTokenSigningKey = CreateRSA();
??? var result = new AccessTokenResult(accessToken);
??? return result;
}
?前面說了,所有授權(quán)模式都是為了獲取AccessToken,授權(quán)碼模式和用戶名密碼模式還有個RefreshToken,當(dāng)然授權(quán)碼模式獨有Authorization Code。一般來說,這三個東西,對于客戶端是一個經(jīng)過加密編碼的字符串,對于服務(wù)端是可序列化的對象,存儲相關(guān)授權(quán)信息。需要注意的是客戶端證書模式?jīng)]有RefreshToken,這是為什么呢?我們不妨想想為什么授權(quán)碼模式和用戶名密碼模式有個RefreshToken,或者說RefreshToken的作用是什么。以下是我個人推測:
?
首先要明確,AccessToken一般是不會永久有效的。因為,AccessToken并沒有承載可以驗證客戶端身份的完備信息,并且資源服務(wù)端也不承擔(dān)驗證客戶端身份的職責(zé),一旦AccessToken被他人獲取,那么就有可能被惡意使用。失效機制有效減少了產(chǎn)生此類事故可能造成的損失。當(dāng)AccessToken失效后,需要重新獲取。對于授權(quán)碼模式和用戶名密碼模式來說,假如沒有RefreshToken,就意味這需要用戶重新輸入用戶名密碼進行再次授權(quán)。如果AccessToken有效期夠長,比如幾天,倒不覺得有何不妥,有些敏感應(yīng)用只設(shè)置數(shù)分鐘,就顯得不夠人性化了。為了解決這個問題,引入RefreshToken,它會在AccessToken失效后,在不需要用戶參與的情況下,重新獲取新的AccessToken,這里有個前提就是RefreshToken的有效期(如果有的話)要比AccessToken長,可設(shè)為永久有效。那么,RefreshToken泄露了會帶來問題嗎?答案是不會,除非你同時泄露了客戶端身份憑證。需要同時具備RefreshToken和客戶端憑證信息,才能獲取新的AccessToken,我們甚至可以將舊的AccessToken當(dāng)作RefreshToken。同理可推,由于不需要用戶參與授權(quán),在客戶端證書模式下,客戶端在AccessToken失效后只需提交自己的身份憑證重新請求新AccessToken即可,根本不需要RefreshToken。
授權(quán)碼模式,用戶授權(quán)后(此時并生成返回AccessToken,而是返回授權(quán)碼),授權(quán)服務(wù)端要保存相關(guān)的授權(quán)信息,為此定義一個ClientAuthorization類:
public class ClientAuthorization
{
??? public int ClientId { get; set; }
??? public string UserId { get; set; }
??? public string Scope { get; set; }
??? public DateTime? ExpirationDateUtc { get; set; }
}
?ClientId和UserId就不說了,Scope是授權(quán)范圍,可以是一串Uri,也可以是其它標(biāo)識,只要后臺代碼能通過它來判斷待訪問資源是否屬于授權(quán)范圍即可。ExpirationDateUtc乃是授權(quán)過期時間,即當(dāng)該時間到期后,需要用戶重新授權(quán)(有RefreshToken)也沒用,為null表示永不過期。
?
資源服務(wù)端
在所有的授權(quán)模式下,資源服務(wù)端都只專注一件和OAuth相關(guān)的事情——驗證AccessToken。這個步驟相對來說就簡單很多,以Asp.net WebAPI為例。在此之前建議對Asp.net WebAPI消息攔截機制不熟悉的朋友瀏覽一遍 ASP.NET Web API之消息[攔截]處理 。這里我們新建一個繼承自DelegatingHandler的類作為例子:
public class BearerTokenHandler : DelegatingHandler
{
??? /// <summary>
??? /// 驗證訪問令牌合法性,由授權(quán)服務(wù)器私鑰簽名,資源服務(wù)器通過對應(yīng)的公鑰驗證
??? /// </summary>
??? private static readonly RSAParameters AuthorizationServerSigningPublicKey = new RSAParameters();//just a 例子
??? private RSACryptoServiceProvider CreateAuthorizationServerSigningServiceProvider()
??? {
??????? var authorizationServerSigningServiceProvider = new RSACryptoServiceProvider();
??????? authorizationServerSigningServiceProvider.ImportParameters(AuthorizationServerSigningPublicKey);
??????? return authorizationServerSigningServiceProvider;
??? }
??? protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
??? {
??????? if (request.Headers.Authorization != null)
??????? {
??????????? if (request.Headers.Authorization.Scheme == "Bearer")
??????????? {
??????????????? var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(this.CreateAuthorizationServerSigningServiceProvider(), null));
??????????????? var principal = resourceServer.GetPrincipal(request);//可以在此傳入待訪問資源標(biāo)識參與驗證
??????????????? HttpContext.Current.User = principal;
??????????????? Thread.CurrentPrincipal = principal;
??????????? }
??????? }
??????? return base.SendAsync(request, cancellationToken);
??? }
}
?
需要注意,AccessToken乃是從頭信息Authorization獲取,格式為“Bearer:AccessToken”,在下文“ 原生方式獲取AccessToken ”中有進一步描述。ResourceServer.GetPrincipal方法使用授權(quán)服務(wù)端的公鑰驗證AccessToken的合法性,同時解密AccessToken,若傳入?yún)?shù)有scope,則還會判斷scope是否屬于授權(quán)范圍內(nèi),通過后將會話標(biāo)識賦給當(dāng)前會話,該會話標(biāo)識乃是當(dāng)初用戶授權(quán)時的用戶信息,這樣就實現(xiàn)了用戶信息的傳遞。一般來說若返回的principal為null,就可以不必執(zhí)行后續(xù)邏輯了。
?
客戶端
可以認為DotNetOpenAuth.OAuth2.Client是DotNetOpenAuth給C#客戶端提供的默認SDK。我們以授權(quán)碼模式為例。先聲明一個IAuthorizationState接口對象,IAuthorizationState接口是用來保存最終換取AccessToken成功后授權(quán)服務(wù)端返回的信息,其部分定義如下:
public interface IAuthorizationState {
??? Uri Callback { get; set; }
??? string RefreshToken { get; set; }
??? string AccessToken { get; set; }
??? DateTime? AccessTokenIssueDateUtc { get; set; }
??? DateTime? AccessTokenExpirationUtc { get; set; }
??? HashSet<string> Scope { get; }
}
?
AccessTokenExpirationUtc是AccessToken過期時間,以Utc時間為準(zhǔn)。若該對象為null,則表示尚未授權(quán),我們需要去授權(quán)服務(wù)端請求。
?
private static AuthorizationServerDescription _authServerDescription = new AuthorizationServerDescription
{
??? TokenEndpoint = new Uri(MvcApplication.TokenEndpoint),
??? AuthorizationEndpoint = new Uri(MvcApplication.AuthorizationEndpoint),
};
private static WebServerClient _client = new WebServerClient(_authServerDescription, "democlient", "samplesecret");
[HttpPost]
public ActionResult Index()
{
??? if (Authorization == null)
??? {
??????? return _client.PrepareRequestUserAuthorization().AsActionResult();
??? }
??? return View();
}
?
AuthorizationServerDescription包含兩個屬性,AuthorizationEndpoint是用戶顯式授權(quán)的地址,一般即用戶輸用戶名密碼的地;TokenEndpoint是用授權(quán)碼換取AccessToken的地址,注意該地址須用POST請求。“democlient”和“samplesecret”是示例用的客戶端ID和客戶端Secret。WebServerClient.PrepareRequestUserAuthorization方法將會首先返回code和state到當(dāng)前url,以querystring的形式(若用戶授權(quán)的話)。
?
code即是授權(quán)碼,state參數(shù)不好理解,這涉及到CSRF,可參看淺談CSRF攻擊方式,state就是為了預(yù)防CSRF而引入的隨機數(shù)。客戶端生成該值,將其附加到state參數(shù)的同時,存入用戶Cookie中,用戶授權(quán)完畢后,該參數(shù)會同授權(quán)碼一起返回到客戶端,然后客戶端將其值同Cookie中的值比較,若一樣則表示該次授權(quán)為當(dāng)前用戶操作,視為有效。由于不同域的cookie無法共享,因此其它站點并不能知道state的確切的值,CSRF攻擊也就無從談起了。簡單地說,state參數(shù)起到一個標(biāo)示消息是否合法的作用。結(jié)合獲取授權(quán)碼這步來說,授權(quán)服務(wù)端返回的url為?http://localhost:22187/?code=xxxxxxxxx&state=_PzGpfJzyQI9DkdoyWeWr?格式,若忽略state,那么攻擊方將code替換成自己的授權(quán)碼,最終客戶端獲取的AccessToken是攻擊方的AccessToken,由于AccessToken同用戶關(guān)聯(lián),也就是說,后續(xù)客戶端做的其實是另一個用戶資源(也許是攻擊方注冊的虛擬用戶),如果操作中包括新增或更新,那么真實用戶信息就會被攻擊方獲取到。可參看 OAuth2 Cross Site Request Forgery, and state parameter 。
有了code就可以去換取AccessToken了:
public ActionResult Index(string code,string state)
{
??? if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state))
??? {
??????? var authorization = _client.ProcessUserAuthorization(Request);
??????? Authorization = authorization;
??????? return View(authorization);
??? }
??? return View();
}
如前所述,Authorization不為null即表示整個授權(quán)流程成功完成。然后就可以用它來請求資源了。
public ActionResult Invoke()
{
??? var request = new HttpRequestMessage(new HttpMethod("GET"), "http://demo.openapi.cn/bookcates");
??? using (var httpClient = new HttpClient(_client.CreateAuthorizingHandler(Authorization)))
??? {
??????? using (var resourceResponse = httpClient.SendAsync(request))
??????? {
??????????? ViewBag.Result = resourceResponse.Result.Content.ReadAsStringAsync().Result;
??????? }
??? }
??? return View(Authorization);
}
WebServerClient.CreateAuthorizingHandler方法返回一個DelegatingHandler,主要用來當(dāng)AccessToken過期時,使用RefreshToken刷新?lián)Q取新的AccessToken;并設(shè)置Authorization頭信息,下文有進一步說明。
原生方式獲取AccessToken
既然是開放平臺,面對的客戶端種類自然多種多樣,DotNetOpenAuth.OAuth2.Client顯然就不夠用了,我也不打算為了這個學(xué)遍所有程序語言。所幸OAuth基于http,不管任何語言開發(fā)的客戶端,獲取AccessToken的步驟本質(zhì)上就是提交http請求和接收http響應(yīng)的過程,客戶端SDK只是將這個過程封裝得更易用一些。下面就讓我們以授權(quán)碼模式為例,一窺究竟。
參照前述事例,當(dāng)我們第一次(新的瀏覽器會話)在客戶端點擊“請求授權(quán)”按鈕后,會跳轉(zhuǎn)到授權(quán)服務(wù)端的授權(quán)界面。
可以看到,url中帶了client_id、redirect_uri、state、response_type四個參數(shù),若要請求限定的授權(quán)范圍,還可以傳入scope參數(shù)。其中response_type設(shè)為code表示請求的是授權(quán)碼。
private string GetNonCryptoRandomDataAsBase64(int binaryLength)
{
??? byte[] buffer = new byte[binaryLength];
??? _random.NextBytes(buffer);
??? string uniq = Convert.ToBase64String(buffer);
??? return uniq;
}
public ActionResult DemoRequestCode()
{
??? string xsrfKey = this.GetNonCryptoRandomDataAsBase64(16);//生成隨機數(shù)
??? string url = MvcApplication.AuthorizationEndpoint + "?" +
??????? string.Format("client_id={0}&redirect_uri={1}&response_type={2}&state={3}",
??????? "democlient", "http://localhost:22187/", "code", xsrfKey);
??? HttpCookie xsrfKeyCookie = new HttpCookie(XsrfCookieName, xsrfKey);
??? xsrfKeyCookie.HttpOnly = true;
??? xsrfKeyCookie.Secure = FormsAuthentication.RequireSSL;
??? Response.Cookies.Add(xsrfKeyCookie);
}
?
授權(quán)碼返回后,先檢查state參數(shù),若通過則換取AccessToken:
?
private bool VerifyState(string state)
{
??? var cookie = Request.Cookies[XsrfCookieName];
??? if (cookie == null)
??????? return false;
??? var xsrfCookieValue = cookie.Value;
??? return xsrfCookieValue == state;
}
private AuthenticationHeaderValue SetAuthorizationHeader()
{
??? string concat = "democlient:samplesecret";
??? byte[] bits = Encoding.UTF8.GetBytes(concat);
??? string base64 = Convert.ToBase64String(bits);
??? return new AuthenticationHeaderValue("Basic", base64);
}
public ActionResult Demo(string code, string state)
{
??? if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state) && VerifyState(state))
??? {
??????? var httpClient = new HttpClient();
??????? var httpContent = new FormUrlEncodedContent(new Dictionary<string, string>()
??? {
??????? {"code", code},
??????? {"redirect_uri", "http://localhost:22187/"},
??????? {"grant_type","authorization_code"}
??? });
??????? httpClient.DefaultRequestHeaders.Authorization = this.SetAuthorizationHeader();
??????? var response = httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).Result;
??????? Authorization = response.Content.ReadAsAsync<AuthorizationState>().Result;
??????? return View(Authorization);
??? }
??? return View();
}
?
如上所示,以Post方式提交,三個參數(shù),code即是授權(quán)碼,redirect_uri和獲取授權(quán)碼時傳遞的redirect_uri要保持一致,grant_type設(shè)置為“authorization_code”。注意 SetAuthorizationHeader方法 ,需要設(shè)置請求頭的Authorization屬性,key為“Basic”,值為以Base64編碼的“客戶端ID:客戶端Secret”字符串, 至于為何要如此規(guī)定,暫時沒有探究 。 成功后返回的信息可以轉(zhuǎn)為前面說的IAuthorizationState接口對象。?
?
如前所述,當(dāng)AccessToken過期后,需要用RefreshToken刷新。
private void RefreshAccessToken()
{
??? var httpClient = new HttpClient();
??? var httpContent = new FormUrlEncodedContent(new Dictionary<string, string>()
??? {
??????? {"refresh_token", Authorization.RefreshToken},
??????? {"grant_type","refresh_token"}
??? });
??? httpClient.DefaultRequestHeaders.Authorization = this.SetAuthorizationHeader();
??? var response = httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).Result;
??? Authorization = response.Content.ReadAsAsync<AuthorizationState>().Result;
}
?
其中g(shù)rant_type須設(shè)置為”refresh_token”,請求頭信息設(shè)置同前。
?
獲取AccessToken后, 就可以用于訪問用戶資源了。
public ActionResult DemoInvoke()
{
??? var httpClient = new HttpClient();
??? if (this.Authorization.AccessTokenExpirationUtc.HasValue && this.Authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow)
??? {
??????? this.RefreshAccessToken();
??? }
??? var bearerToken = this.Authorization.AccessToken;
??? httpClient = new HttpClient();
??? httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
??? var request = new HttpRequestMessage(new HttpMethod("GET"), "http://demo.openapi.cn/bookcates");
??? using (var resourceResponse = httpClient.SendAsync(request))
??? {
??????? ViewBag.Result = resourceResponse.Result.Content.ReadAsStringAsync().Result;
??? }
??? return View(Authorization);
}
用法很簡單, Authorization 請求頭,key設(shè)為“Bearer”,值為AccessToken即可。
總結(jié)
以上是生活随笔為你收集整理的【转】C#搭建Oauth2.0认证流程以及代码示例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 商家虚假宣传牛磺酸泡腾片有催迷效果 阿里
- 下一篇: 腾讯下架QQ影音所有版本:曾被暴风公司排