談談基於OAuth 2.0的第三方認證
雖然我們在《上篇》分別討論了4種預定義的Authorization Grant型別以及它們各自的適用場景的獲取Access Token的方式,我想很多之前沒有接觸過OAuth 2.0的讀者朋友們依然會有“不值所云” 之感,所以在介紹的內容中,我們將採用例項演示的方式對Implicit和Authorization Code這兩種常用的Authorization Grant作深入介紹。本章著重介紹Implicit Authorization Grant。
Implicit Authorization Grant授權流程
假設我們的客戶端應用整合了Windows Live Connect API認證服務,並且在成功取得使用者授權並得到Access Token之後呼叫相應的Web API獲取當前登入使用者的個人資訊。一般來說,Implicit型別的Authorization Grant大都被將瀏覽器作為執行上下文的客戶端應用採用,換句話說,這樣的客戶端就是在瀏覽器中執行的JavaScript程式。下圖體現了這樣一個採用Implicit型別的Authorization Grant的客戶端應用取得授權、得到Access Token並最終獲取到受保護資源(登入使用者個人資訊)的完整流程。
如右圖所示,使用者會先被客戶端應用重定向到授權伺服器(login.live.com),具體的地址為“
response_type: 表示請求希望獲取的物件型別,在此我們希望獲取的是Access Token,所以這裡指定的值為“token”。
redirect_uri: 表示授權伺服器在獲得使用者授權並完成對使用者的認證之後重定向的地址,Access Token就以Hash(#)的方式附加在該URL後面。客戶端應用利用這個地址接收Access Token。
client_id: 唯一標識被授權客戶端應用的ClientID。
scope: 表示授權的範圍,如果採用“wl.signin”意味著允許使用者從客戶端應用直接登入到Live Services,如果Scope為“wl.basic”則表示執行客戶端應用獲取聯絡人資訊。如果讀者朋友希望瞭解Windows Live Connect具體支援那些Scope,可以查閱Windows Live Connect API的官方文件。
如果當前使用者尚未登入到Windows Live Services,登入視窗將會出現,當使用者輸入正確Windows Live帳號和密碼併成功透過認證之後,瀏覽器其上會出現如下圖所示的授權頁面,具體需要授予的許可權集取決於上面介紹的Scope引數。我們點選“Yes”按鈕完成授權,成功授權之後,這個的授權頁面在後續的請求中將不會再出現。
授權伺服器在獲取使用者的授權之後,會生成一個Access Token。接下來,它會提取請求中指定的重定向地址(即redirect_uri引數),然後將生成的Access Token以Hash(#)的形式附加在該地址後面,最終針對這個攜帶有Access Token的新地址返回一個重定向的響應。如第一張圖所示,我們採用的重定向地址為“{accesstoken}”上。
這個重定向地址對應著客戶端應用需要獲取授權資源的頁面,該頁面可以直接從代表當前地址的URL中獲得Access Token,並利用它來獲取目標資源。對於我們的例子來說,它需要獲取當前Windows Live帳號的基本資訊,請求的地址為“ Token以查詢字串的形式(“?access_token={accesstoken}”)提供給資源伺服器,後者據此驗證請求的合法性並在驗證成功的情況下將當前使用者的基本資訊以JSON的形式返回給客戶端應用。
例項演示:建立採用Implicit Authorization Grant的Web API應用
接下來我們建立一個ASP.NET Web API程式來實現上面這個應用場景。我們首先需要按照《上篇》介紹的流程為該應用註冊一個ClientID,如果我們已經在Windows Live Connect上建立了一個應用,我們可以直接使用該應用的ClientID。
假設我們在Windows Live Connect建立了一個採用“%windir%System32driversetchosts”)將此域名對映為本機的IP地址(127.0.0.1),具體的對映指令碼如下所示。除此之外,由於我們採用HTTPS並且採用本地IIS作為宿主,所以我們需要為Web API應用所在的站點新增一個HTTPS繫結。
1: 127.0.0.1
在具體介紹認證實現原理之前,我們不妨先來演示一下最終達到的效果。我們在ASP.NET Web API應用中定義瞭如下一個繼承自ApiController的DemoController,它具有唯一一個用於獲取當前登入使用者個人基本資訊的Action方法GetProfile。在該方法中,它透過我們定義的擴充套件方法TryGetAccessToken從當前請求中提取Access Token,然後利用它呼叫Windows Live Connect提供的Web API(
1: [Authenticate("https:///webapi/account/capturetoken")]
2: public class DemoController : ApiController
3: {
4: public HttpResponseMessage GetProfile()
5: {
6: string accessToken;
7: if (this.Request.TryGetAccessToken(out accessToken))
8: {
9: using (HttpClient client = new HttpClient())
10: {
11: string address = string.Format("{0}", accessToken);
12: return client.GetAsync(address).Result;
13: }
14: }
15: return new HttpResponseMessage(HttpStatusCode.BadRequest) { ReasonPhrase = "No access token" };
16: }
17: }
整合Windows Live Connect認證的實現最終是透過應用在DemoController型別上的AuthenticateAttribute特性來完成的,這是一個AuthenticationFilter,作為引數的URL指向一個用於獲取和轉發Access Token的Web頁面。現在我們直接利用瀏覽器來呼叫定義在DemoController中的Action方法GetProfile,如果當前使用者尚未登入到Windows Live,瀏覽器會自動重定向到Windows Live的登入介面。當我們輸入正確Windows Live帳號和密碼後,當前使用者的基本資訊以JSON格式顯示在瀏覽器上(如果尚未對該應用進行授權,如上圖所示的頁面會呈現出來),具體的效果如下圖所示。
應用在DemoController上的AuthenticateAttribute特性完成了針對授權頁面的重定向和Access Token的請求和接收。除此之外,為了讓瀏覽器能夠在第一次認證之後能夠自動地傳送Access Token,我們利用AuthenticateAttribute將Access Token寫入了Cookie之中,這與Forms認證比較類似。不過就安全的角度來講,利用Cookie攜帶安全令牌會引起一種被稱為“跨站請求偽造(CSRF:Cross-Site Request Forgery)”的安全問題,所以透過HTTP報頭來作為安全令牌的載體是更安全的做法。
如下所示的程式碼片斷體現了整個AuthenticateAttribute特性的定義,我們可以看到它同時實現了IAuthenticationFilter和IActionFilter。字串常量CookieName表示攜帶Access Token的Cookie名稱,只讀屬性CaptureTokenUri表示授權伺服器傳送Access Token採用的重定向地址,它指向一個我們由我們設計的Web頁面,該頁面在接受到Access Token之後會自動向目標資源所在的地址傳送一個請求,該請求地址以查詢字串的形式攜帶此Access Token。(之所以我們需要利用一個Web頁面在客戶端(瀏覽器)接收並重發Access Token,是因為授權伺服器將返回的Access Token至於重定向URI的Hash(#)部分,所以在服務端是獲取不到的,只能在客戶端來收集。這個Web頁面的目的在在於在客戶端獲取的Access Token併傳送到服務端。)
1: [AttributeUsage(AttributeTargets.Class| AttributeTargets.Method)]
2: public class AuthenticateAttribute : FilterAttribute, IAuthenticationFilter, IActionFilter
3: {
4: public const string CookieName = "AccessToken";
5: public string CaptureTokenUri { get; private set; }
6:
7: public AuthenticateAttribute(string captureTokenUri)
8: {
9: this.CaptureTokenUri = captureTokenUri;
10: }
11:
12: public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
13: {
14: return Task.FromResult
15: }
16:
17: public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
18: {
19: string accessToken;
20: if (!context.Request.TryGetAccessToken(out accessToken))
21: {
22: string clientId = "000000004810C359";
23: string redirectUri = string.Format("{0}?requestUri={1}", this.CaptureTokenUri, context.Request.RequestUri);
24: string scope = "wl.signin%20wl.basic";
25:
26: string uri = "";
27: uri += "?response_type=token";
28: uri += "&redirect_uri={0}&client_id={1}&scope={2}";
29: uri = String.Format(uri, redirectUri, clientId, scope);
30: context.Result = new RedirectResult(new Uri(uri), context.Request);
31: }
32: return Task.FromResult
33: }
34:
35: public TaskExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func > continuation)
36: {
37: HttpResponseMessage response = continuation().Result;
38: string accessToken;
39: if (actionContext.Request.TryGetAccessToken(out accessToken))
40: {
41: response.SetAccessToken(actionContext.Request, accessToken);
42: }
43: return Task.FromResult(response);
44: }
45: }
在實現的ChallengeAsync方法(該方法在認證過程中向客戶端傳送“質詢”響應)中,我們利用自定義的擴充套件方法TryGetAccessToken試著從當前請求中獲取攜帶的Access Token。如果這樣的Access Token不存在,我們透過為HttpAuthenticationChallengeContext的Result屬性設定一個RedirectResult物件實現針對Windows Live Connect授權頁面的重定向,相關的引數(respone-type、redirect_uri、client_id和scope)以查詢字串的形式提供。
值得一提的作為重定向地址的引數redirect_uri,我們會將當前請求的地址作為查詢字串(名稱為“requestUri”)附加到CaptureTokenUri上得到的URI作為該引數的值,當前請求的地址正式Web頁面傳送Access Token的目標地址。
另一個實現的ExecuteActionFilterAsync方法複雜將Access Token寫入響應Cookie之中,具體的操作實現在我們自定義的擴充套件方法SetAccessToken中。下面的程式碼片斷給出了兩個擴充套件方法SetAccessToken和TryGetAccessToken的定義。
1: public static class Extensions
2: {
3: public static bool TryGetAccessToken(this HttpRequestMessage request, out string accessToken)
4: {
5: //從Cookie中獲取Access Token
6: accessToken = null;
7: CookieHeaderValue cookieValue = request.Headers.GetCookies(AuthenticateAttribute.CookieName).FirstOrDefault();
8: if (null != cookieValue)
9: {
10: accessToken = cookieValue.Cookies.FirstOrDefault().Value;
11: return true;
12: }
13:
14: //從查詢字串中獲取Access Token
15: accessToken = HttpUtility.ParseQueryString(request.RequestUri.Query)["access_token"];
16: return !string.IsNullOrEmpty(accessToken);
17: }
18:
19: public static void SetAccessToken(this HttpResponseMessage response, HttpRequestMessage request, string accessToken)
20: {
21: if (request.Headers.GetCookies(AuthenticateAttribute.CookieName).Any())
22: {
23: return;
24: }
25: CookieHeaderValue cookie = new CookieHeaderValue(AuthenticateAttribute.CookieName, accessToken)
26: {
27: HttpOnly = true,
28: Path = "/"
29: };
30: response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
31: }
32: }
在我們演示的例項中,應用在DemoController型別上的AuthenticateAttribute特性的CaptureTokenUri屬性(“https:///webapi/account/capturetoken”)指向定義在AccountController這麼一個Controller(ASP.NET MVC的Controller,不是ASP.NET Web API的HttpController)的Action方法CaptureToken,具體定義如下所示。
1: public class AccountController : Controller
2: {
3: public ActionResult CaptureToken(string requestUri)
4: {
5: ViewBag.RequestUri = requestUri;
6: return View();
7: }
8: }
由於AuthenticateAttribute在呼叫Windows Live Connect的API獲取Access Token所指定的重定向地址具有一個名為“requestUri”的查詢字串,其值正好是呼叫Web API的地址,該地址會自動繫結到Action方法CaptureToken的requestUri引數上。如果上面的程式碼片斷所示,該方法會將該地址以ViewBag的形式傳遞到呈現的View之中。
1:
2:
3:
4:
5: $(document).ready(function () {
6: var redirectUri = '@ViewBag.RequestUri';
7: if (redirectUrl.indexOf('?') >= 0) {
8: redirectUrl += "&" + location.hash.slice(1)
9: }
10: else {
11: redirectUrl += "?" + location.hash.slice(1)
12: }
13: location.href = redirectUri;
14: });
15:
16:
17:
上面的程式碼片斷代表Action方法CaptureToken對應View的定義。在該View中,我們從當前地址的Hash(#)部分得到Access Token,並將其作為查詢字串附加到從ViewBag中得到的資源訪問地址上,並透過設定location的href屬性的方式攜帶Access Token對Web API再次發起呼叫。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3137/viewspace-2805185/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 基於Oauth2.0實現SSO單點認證OAuth
- OAuth 2.0 授權碼認證OAuth
- OAuth 2.0 授權認證詳解OAuth
- OAuth2.0認證授權workflow探究OAuth
- API介面設計 OAuth2.0認證APIOAuth
- 談談HTML的基礎認知HTML
- OAuth2.0實戰!使用JWT令牌認證!OAuthJWT
- 基於oauth 2.0 實現第三方開放平臺OAuth
- 微服務架構 | 7.1 基於 OAuth2 的安全認證微服務架構OAuth
- Spring Cloud Security OAuth2.0 認證授權系列(一) 基礎概念SpringCloudOAuth
- 在Spring Boot中實現OAuth2.0認證Spring BootOAuth
- 談談前後端分離及認證選擇後端
- Spring Security OAuth2.0認證授權四:分散式系統認證授權SpringOAuth分散式
- [WCF安全系列]談談WCF的客戶端認證[使用者名稱/密碼認證]客戶端密碼
- Passport OAuth 認證 解析PassportOAuth
- Spring Security OAuth2.0認證授權三:使用JWT令牌SpringOAuthJWT
- 談談HTTPS安全認證,抓包與反抓包策略HTTP
- 純 JavaScript 實現的 OAuth 認證JavaScriptOAuth
- OAuth2.0實戰:認證、資源服務異常自定義!OAuth
- Java OAuth 2.0 客戶端程式設計(三):認證碼授權JavaOAuth客戶端程式設計
- 【認證與授權】2、基於session的認證方式Session
- 談談Markdown的認識與入門
- Spring Security OAuth2.0認證授權二:搭建資源服務SpringOAuth
- WebApi的建立,部署,Oauth身份認證(一)WebAPIOAuth
- 高可用!一個基於 SpingBoot + Oauth2 的單點認證授權中心!bootOAuth
- iOS 新浪微部落格戶端Demo實踐之(一)OAuth2.0認證iOSOAuth
- vsftpd基於mysql的認證方式FTPMySql
- OAuth 2.0OAuth
- 面試官:談談你對mysql索引的認識?面試MySql索引
- 淺談ElasticSearch的認知Elasticsearch
- “談談MySQL的基數統計”MySql
- Spring Cloud實戰系列(九) - 服務認證授權Spring Cloud OAuth 2.0SpringCloudOAuth
- Kubernetes客戶端認證(二)—— 基於ServiceAccount的JWTToken認證客戶端JWT
- 曾奇:談談我所認識的分散式鎖分散式
- 談談對資料架構的幾點認識架構
- 使用 OAuth 2.0 進行 Kafka 身份驗證 - strimziOAuthKafka
- Martin Fowler談Scrum認證、敏捷現狀與未來Scrum敏捷
- 認證授權:學習OAuth協議OAuth協議