從安全的角度來講,《中篇》介紹的Implicit類型的Authorization Grant存在這樣的兩個問題:其一,授權服務器沒有對客戶端應用進行認證,因為獲取Access Token的請求只提供了客戶端應用的ClientID而沒有提供其ClientSecret;其二,Access Token是授權服務器單獨頒發給客戶端應用的,照理說對于其他人(包括擁有被訪問資源的授權者)應該是不可見的。Authorization Code類型的Authorization Grant很好地解決了這兩個問題。
Authorization Code Authorization Grant授權流程
Authorization Code是最為典型的Authorization Grant,它“完美”地實現了指定的OAuth初衷:資源擁有者可以在向客戶端應用提供自身憑證的前提下授權它獲取受保護的資源。如右圖所示,Authorization Code類型的Authorization Grant具有完整的“三段式”授權流程,接下來,我們還要針對“集成Windows Live Connect認證 獲取當前用戶個人信息”這個應用場景來討論一下Authorization Code類型的Authorization Grant的具體授權流程。
Implicit類型的Authorization Grant授權的客戶端運行于存客戶端(瀏覽器)上下文環境,Authorization Code類型的Authorization Grant則適用于運行于服務器的應用,比如ASP.NET MVC應用的Controller,或者是定義在View中的服務端程序。右圖體現的就是在服務器(www.artech.com)運行的客戶端應用[1]。
上面我們已經說過,Authorization Code類型Authorization Grant具有與Kerberos類似的授權方式。如果我們將Access Token看作為了獲取受保護資源而“登堂入室”的入場券的話,Authorization Code就是購買這張入場券的“認購權證”。客戶端應用需要首先取得Authorization Code,因為它代表了資源擁有者對它的授權,并且是獲取Access Token時必須提供的憑證。
客戶端應用首先向授權服務器發送一個獲取Authorization Code的請求,請求的地址同樣為“https://login.live.com/oauth20_authorize.srf”,相應的參數同樣以查詢字符串的形式提供。與Implicit類型Authorization Grant獲取Access Token的請求一樣,此時需要提供如下4個完全一樣的參數。
- response_type:表示請求希望獲取的對象類型,在此我們希望獲取的是Authorization Code,所以這里指定的值為“code”。
- redirect_uri:表示授權服務器在獲得用戶授權并完成對用戶的認證之后重定向的地址,Authorization Code就以查詢字符串(?code={authorizationcode})的方式附加在該URL后面。客戶端應用利用這個地址接收Authorization Code。
- client_id: 唯一標識被授權客戶端應用的ClientID。
- scope:表示授權的范圍,根據具體需要的權限集而定。
如果當前用戶尚未登錄但Windows Live Services,他會被自動重定向到登錄頁面。在尚未對客戶端應用進行授權的情況下,如左圖所示的授權頁面會顯示出來。在取得登錄用戶的授權之后,授權服務器會返回一個重定向的響應,而請求提供的redirect_uri參數值直接作為重定向地址。由授權服務器生成的Authorization Code就以查詢字符串(?code={authorizationcode})的方式附加在重定向URL的后面。重定向的請求被客戶端應用接收后,Authorization Code被提取并保存起來。
接下來客戶端應用會利用得到的Authorization Code向授權服務器獲取Access Token,這一般為HTTP-POST請求。作為請求消息主體傳遞的內容除了作為參數“code”的Authorization Code之外,還包含如下一些必需的參數。
- client_id: 唯一標識被授權客戶端應用的ClientID。
- client_secret:唯一標識被授權客戶端應用的ClientSecret。
- redirect_uri:之前獲取Authorization Code時指定的重定向地址。
- grant_type:采用的Authorization Grant類型,參數值為“ authorization_code”。
授權服務器接受到請求之后,除了利用提供的ClientID和ClientSecrete對客戶端應用實施驗證之外,還會檢驗之前獲取Authorization Code提供的ClientID和重定向地址是否與本次提供的一致。成功完成檢驗之后,授權服務器會生成一個Access Token作為響應內容發送給客戶端應用。整個響應內容除了Access Token之外,還包含其他一些與之相關的屬性。
1: {
2: "token_type":"bearer",
3: "expires_in":3600,
4: "scope":"wl.signin wl.basic",
5: "access_token":"EwAwAq1DBAAUGCCXc8wU/zFu9QnLdZXy+YnElFkAAUJrhIwUzWhsNUTrrNst0ThiSSQ4633vMMkJVIWC9o7RF5Fbml42RptWs8+fg1pIWQsroN+0tTB3+uIFtI2ZjWY+E1Fv40WAU7SmvbIJ8CTkxvCSC96ie/kgV0Q+TvYFGZYRbXhMwc2pqY2LxWp0DKbSmKWXUmJn5/tf48a8n7HLBjc8fcrWfr1ff99lBSgCri5AGsEeVWH2/UpkUmVMazfBFqJNdaZyrQ8HmIgWWcfPI3B1mrIRFWprlIMNdF6nERiOExdTcCK6xqM3HhLtwHtqLKXMa0N568hR4xn1FYSXqgAjCWllvJ1BT51g0YDQygefL4ynmo7H/2rjPuKS70EDZgAACI4PXp7hCnaKAAGzOZCLbNQd1ucG/bLSEq23hEAFKX9vdmG1IUOVF2X+/tV2G5ZXnj1QL/F2WSW4dOpnU41lUnMZr+hOSq7ljF9d2IMOyDpHKuzTavUQO6GvxHoPuLMhZGP0kzYye+ASdHT2Ave6cBisSp6e/EIRZDRWyUfuAjg9mk5NKdQlFjQyKLMIiBupLKqJMN3Mdld/R412V3w1JQStB0kM93nV99H4ouSMq1sj13sJpLhUesnuSK6XfG9RcVo2hioy28qt4SoZxL8kWaQqsgPRpJ4Mkyu6sRYEAmK5olCqN6L/fNzy6fRXELKzfl33H61zkAllzYoxCKuoof0Mm6nANj1SMpI1AAA=",
6: "authentication_token":"eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJ2ZXIiOjEsImlzcyI6InVybjp3aW5kb3dzOmxpdmVpZCIsImV4cCI6MTM4NTg2MDQ3NiwidWlkIjoiNzY4YjMxYjU3NjFlN2EzMTIzNzk5ZjIzNzFjMDIxOGEiLCJhdWQiOiJ3d3cuYXJ0ZWNoLmNvbSIsInVybjptaWNyb3NvZnQ6YXBwdXJpIjoiYXBwaWQ6Ly8wMDAwMDAwMDQ4MTBDMzU5IiwidXJuOm1pY3Jvc29mdDphcHBpZCI6IjAwMDAwMDAwNDgxMEMzNTkifQ.2qn4MWtekRhkgLoRyiFoB5NmUnQ0oKuqQuqpdfb46os"
7: }
授權服務器返回Access Token的完整響應內容如上所示,我們可以看到這是一個以JSON格式表示的對象。除了Access Token自身的內容之外,還可以獲取其他一些相關的信息,比如Access Token的類型(token_type)、過期時間(“expires_in”,單位為秒)、授權范圍(“scope”,與獲取Authorization Code時指定的一致)以及表示認證身份的安全令牌(“authentication_token”)。
客戶端應用接受到響應之后從中提取出Access Token。當它試圖獲取受保護資源的時候,將此Access Token附加到請求上,便會以授權用戶的名義得到它所需要的資源。對于我們的應用場景來說,客戶端應用直接將Access Token作為請求的查詢字符串(?access_token={accesstoken})訪問地址“https://apis.live.net/v5.0/me”便會成功獲取當前登錄用戶的基本信息。
通過上面對Authorization Code類型的Authorization Grant整個授權流程的介紹,可以看出Implicit Authorization Grant的兩個安全問題得到了很好的解決:雖然客戶端獲取Authorization Code時不需要指定ClientSecret,但是在獲取Access Token時ClientRecret則是必需的,授權服務器只有在成功驗證客戶端應用身份的情況下才會頒發Access Token;針對Access Token的消息交換僅限于授權服務器和客戶端應用之間進行,所以第三方(包括 當前用戶)都無法獲取到正確的Access Token。
Refresh Token
處于安全性考慮,Access Token并非終身有效,而是具有一個過期時間。上面我們給出了授權服務器返回Access Token的響應內容,其“expires_in”屬性表示的就是Access Token的有效期限。那么,Access Token過期之后該如何處理呢?是否需要重新獲得Authorization Code并利用它得到新的Access Token呢?
實際上這是不需要的,當我們得到Authorization Code之后,可以在利用它獲取Access Token的時候,讓授權服務器一并返回一個叫做Refresh Token的令牌。與Access Token不同,Refresh Token是一個長期有效的安全令牌,當Access Token過期之后,我們可以利用它獲取一個新的Access Token。
對于Windows Live Connection來說,如果希望在獲取Access Token的時候讓授權服務器返回一個Refresh Token,其指定的授權范圍必須具有一個名為“wl.offline_access”的Scope,它表示允許客戶端程序在任何時候(包括用戶尚未登錄Windows Live Connect的情況下)讀取和更新用戶信息。對于具有如此授權范圍的Access Token請求,授權服務器返回的響應中會按照如下的形式包含Refresh Code的內容。
1: {
2: "token_type":"bearer",
3: "expires_in":3600,
4: "scope":"wl.signin wl.basic wl.offline_access",
5: "access_token":"EwAwAq1DBAAUGCCXc8wU/zFu9QnLdZXy+YnElFkAAZcfA2Qg/7KeYyCTe+jx4bLz8qTAFTV71leUhqb0XEfZlRHdi/YpTUx5raBNbd2TcqmdPT1p6v7NhZHTvwJg7u+nyEosIIB0hjxDPTEkU8nj6HYZ96OP29Vr6rVbWer5tczd5ez7Hm/GOSTcC2c4w7G1hvoh/wpg26Gn/ox5P0dEOiq0FlISC6ADgl9t8feY4SGS0kYOr3MUgH5JMe+ObuoEQavJtxSnXjhr6Vh9Oe8TSAtmsy32f3LMnf/B/8rQHxmGd284OPQlBgH8hy5z0NsrSS6B/4oMFU+oZSYwWaHMjrX2POuM5Wnu3wa6qI3T5a5Zg0qw2KHLy9eMw2a3wz4DZgAACCEjkTQbxjh/AAHTGE/O2koIChcvaQbkt0DQq+lMxtjp0U8rWABcwTz89Vy7zIlz8l9hzAewpiM+W/6Ot1JU9mQKccrVnIKXugVpaqFJbmZ571NPXMI6p7l1uoUR3yPzDBOOKQn5fGeMmyMjZZsMnjQAzm+JxVoLRFnlwZJeTe4BA0x6bAOb/j4T+Nk6I1nTKMuTvFztluWw4oRTMcKNREb35xlbSqiEXnyU214Khc+tiSIeRDMl4mEpHzlSj2iEhzokfIjqaLq1iPW4EQKXYh3i+o44RjZ4effY4jFAe1jtaojRHVZrtq8g6x06LswECPHhH91i2oD8SMzal4DFY6l833XTHbGBoiPiAAA=",
6: "refresh_token":"CgnjZWSPqffDqDkt3NeqFHwMKs7xiwpM2gQx0A8WOGbIAPbAXqJAZOB1lhcEV8BOWvZevk5Uo9hUu*lEa8TKXRiw*V8KE8!jhEOMQ9o*uwj*z!O50hN182OueDdJEKX*V8BZhIS0!1K2Ii9*SYREKJQ2UPd0jQaveo9IA0Hz2cAhQCt13KQ!gRKF5bBlzaJh6WJMkgljNXZceurRdyM6QuURzQQUo7DelfW!O74oiVZiH7z*ffd7OKj3sAdIzAphWdlwIXjXxY45uzIMe4dR16jw1aiB0JQdYCqcQSYG*0M233tsVMQjL3cfo0WrRj!w1F!Xob!0zkquhK1JBqdlKWI72Vih!QAWDgYeXf9e*NjO",
7: "authentication_token":"eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJ2ZXIiOjEsImlzcyI6InVybjp3aW5kb3dzOmxpdmVpZCIsImV4cCI6MTM4NTg2NDM3MSwidWlkIjoiNzY4YjMxYjU3NjFlN2EzMTIzNzk5ZjIzNzFjMDIxOGEiLCJhdWQiOiJ3d3cuYXJ0ZWNoLmNvbSIsInVybjptaWNyb3NvZnQ6YXBwdXJpIjoiYXBwaWQ6Ly8wMDAwMDAwMDQ4MTBDMzU5IiwidXJuOm1pY3Jvc29mdDphcHBpZCI6IjAwMDAwMDAwNDgxMEMzNTkifQ.ETKELC41Nr2CQq9Pwjf_c3lO0egLibnt5K1D4pdOsDs"
8: }
在客戶端應用從響應內容成功提取出Refresh Token之后,可以在任何時候向授權服務器(地址依然是“https://login.live.com/oauth20_authorize.srf”)發送獲取新的Access Token的請求。和直通過Authorization Code獲取Access Token一樣,這通常也是一個HTTP-POST請求,其主體內容攜帶如下的參數。
- client_id: 唯一標識被授權客戶端應用的ClientID。
- client_secret:唯一標識被授權客戶端應用的ClientSecret。
- redirect_uri:之前獲取Authorization Code時指定的重定向地址。
- grant_type:采用的Authorization Grant類型,這里自然就是“ refresh_code”。
- refresh_token:之前利用Authorization Code獲取的Access Token。
授權服務器對請求作必要驗證后,會將新的Access Token置于響應的主體內容返回給客戶端應用。完整地響應內容如下所示,我們不難看出:其中不僅僅包含新的Access Token,還返回了一個新的Refresh Token。
1: {
2: "token_type":"bearer",
3: "expires_in":3600,
4: "scope":"wl.signin wl.basic wl.offline_access",
5: "access_token":"EwAwAq1DBAAUGCCXc8wU/zFu9QnLdZXy+YnElFkAAbJsGLlIlryVa2JTkU/efezV4cPCjCtgGw7vSyBtRpQIK5VY1auLZjEZ5KOGpSObATlF7Rpph1Ox1sloSn5ja9Z+GF7lZYhPrxiIIGXdbV/R/URWh/LYMxARg0Upw5LV0+4iV0SB1Y2KqJJvhal/ABvONmBE9K8tDutCPTlRC/bkxfgLTup6YFdEutF8l4WVLngS/Nlpjw0y6QNlmvUbSRovFRuyxkh6XnCGW9cC5glNf1u2SlKQJn8VjRsIwyHCzLBP286NTcXIv9oengFdgJEUTAdnseoCj+OswwL9OFVP1A1f6cX55HyvP3RyyRv3CbG1JTswOWv77sCjH6Ay5lQDZgAACEyxeaH5iRHfAAElgyjGBXQQ6y/elz0WHyCcGd/3815qVIlRceQZuCB1BMioE8rG0s8aOj6i/1ta4Gk5+lfOfVI+XUHE2oRpkFzxgDEjsItvwdiX7hgenJ5dTNG8Pm2J7av6YvEh9kFK2Eub38QxipiBnlHFApHdDxEaafNs22Fw462X/JSIHkqppbtLbJprdu/3VF4dOCPsWPGxz4nsNdrYBcgeMjp5hgsJlumS8THflSn5K9r8JsPVjZwVYByIZdzyuxspNtQiuCihG+8YbuhnfcBNfH6QlnE0U/4sb78MAbVwQ4ERVj+/Atd4EVt6c2j39iizAnT60uiH1dBsHzIivVBnwzJlsv4EAAA=",
6: "refresh_token":"CoLhjJ2f88q*RAehnPXWxVHHZY4ICPiYxIpEn8WVrSNH*56vksuQZasYeDo5PX6fQXC8huqykXVy59FDeuHCEIGZ4HZu2qXhkKaviVqHO0M75j9QBq0!lyV3QKH!SLVqObubOc7CBZa8CNPmNkL6jN23BBcT4UC0XfGLk5dfDyoynRUH*mEnLsfvsrVVo1AGbT2AqVbc*GZWAMRovRGMLD7aPJomAJvh8R*mDJtY59uF!Jt2zzJtmMwjfkT7Y2AR9H5dhmrQcrXzrfd!yVpcLgYKLGBS94CgoaV6kC5b8xfAANcurhG4eDqO37!cpDHVGT69WrvUAJ8omf8x3GuDsJnuY!0D0HlxmDZv9NqlN5qj",
7: "authentication_token":"eyJhbGciOiJIUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJ2ZXIiOjEsImlzcyI6InVybjp3aW5kb3dzOmxpdmVpZCIsImV4cCI6MTM4NTg4MjI1NiwidWlkIjoiNzY4YjMxYjU3NjFlN2EzMTIzNzk5ZjIzNzFjMDIxOGEiLCJhdWQiOiJ3d3cuYXJ0ZWNoLmNvbSIsInVybjptaWNyb3NvZnQ6YXBwdXJpIjoiYXBwaWQ6Ly8wMDAwMDAwMDQ4MTBDMzU5IiwidXJuOm1pY3Jvc29mdDphcHBpZCI6IjAwMDAwMDAwNDgxMEMzNTkifQ.mX2YuSU8Op-13hmiz9h352pvNHGTlAOPwbmKpEFFYt0"
8: }
實例演示:創建采用Authororization Code Authorization Grant的Web API應用
在《中篇》提供的實例中,我們演示了如何利用一個自定義AuthenticationFilter創建一個集成了Windows Live Connect認證的ASP.NET Web API應用。我們在這個實例中采用的Authorization Grant類型為Implicit,現在我們對這個AuthenticationFilter進行改造使之采用Authorization Code類型的Authorization Grant。
如果采用Authorization Code類型的Authorization Grant,客戶端應用直接在Web服務器與授權服務器進行消息交換,所以無需在應用的AuthenticateAttribute特性上再指定一個在瀏覽器中收集和轉發Access Token的Web頁面對應的地址了。如下面的代碼片斷所示,應用在DemoController上的AuthenticateAttribute特性不均有任何參數。按照上面的方式利用瀏覽器來調用定義在DemoController中的Action方法GetProfile,我們依然可以得到希望的效果。
1: [Authenticate]
2: public class DemoController : ApiController
3: {
4: public HttpResponseMessage GetProfile()
5: {
6: //省略實現
7: }
8: }
如下所示的新AuthenticateAttribute的定義,其中將Access Token添加到響應Cookie中的ExecuteActionFilterAsync方法沒有任何變化,我們修改的只是實現自IAuthenticationFilter接口的兩個方法。
1: public class AuthenticateAttribute : FilterAttribute, IAuthenticationFilter, IActionFilter
2: {
3: public const string CookieName = "AccessToken";
4: public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
5: {
6: //從請求中獲取Access Token
7: string accessToken;
8: if (context.Request.TryGetAccessToken(out accessToken))
9: {
10: return Task.FromResult<object>(null);
11: }
12:
13: //從請求中獲取Authorization Code,并利用它來獲取Access Token
14: string authorizationCode;
15: if (context.Request.TryGetAuthorizationCode(out authorizationCode))
16: {
17: string query = string.Format("code={0}", authorizationCode);
18:
19: //但前請求URI去除“?code={authorizationcode}”部分作為rediect_uri參數
20: string callbackUri = context.Request.RequestUri.AbsoluteUri.Replace(query, "").TrimEnd('?');
21: using (HttpClient client = new HttpClient())
22: {
23: Dictionary<string, string> postData = new Dictionary<string, string>();
24: postData.Add("client_id", "000000004810C359");
25: postData.Add("redirect_uri", callbackUri);
26: postData.Add("client_secret", "37cN-CGV9JPzolcOicYwRGc9VHdgvg6y");
27: postData.Add("code", authorizationCode);
28: postData.Add("grant_type", "authorization_code");
29: HttpContent httpContent = new FormUrlEncodedContent(postData);
30: HttpResponseMessage tokenResponse = client.PostAsync("https://login.live.com/oauth20_token.srf", httpContent).Result;
31:
32: //得到Access Token并Attach到請求的Properties字典中
33: if (tokenResponse.IsSuccessStatusCode)
34: {
35: string content = tokenResponse.Content.ReadAsStringAsync().Result;
36: JObject jObject = JObject.Parse(content);
37: accessToken = (string)JObject.Parse(content)["access_token"];
38: context.Request.AttachAccessToken(accessToken);
39:
40: return Task.FromResult<object>(null);
41: }
42: else
43: {
44: return Task.FromResult<HttpResponseMessage>(tokenResponse);
45: }
46: }
47: }
48: return Task.FromResult<object>(null);
49: }
50:
51: public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
52: {
53: string accessToken;
54: if (!context.Request.TryGetAccessToken(out accessToken))
55: {
56: string clientId = "000000004810C359";
57: string redirectUri = context.Request.RequestUri.ToString();
58: string scope = "wl.signin%20wl.basic";
59: string uri = "https://login.live.com/oauth20_authorize.srf";
60: uri += "?response_type=code";
61: uri += "&redirect_uri={0}&client_id={1}&scope={2}";
62: uri = String.Format(url, redirectUri, clientId, scope);
63: context.Result = new RedirectResult(new Uri(uri), context.Request);
64: }
65: return Task.FromResult<object>(null);
66: }
67:
68: public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken,Func<Task<HttpResponseMessage>> continuation)
69: {
70: HttpResponseMessage response = continuation().Result;
71: string accessToken;
72: if (actionContext.Request.TryGetAccessToken(out accessToken))
73: {
74: response.SetAccessToken(actionContext.Request, accessToken);
75: }
76: return Task.FromResult<HttpResponseMessage>(response);
77: }
78: }
在實現的AuthenticateAsync方法中,我們首選調用自定義的擴展方法TryGetAccessToken試著從當前請求中提取Access Token。如果Access Token不存在,我們在調用另一個擴展方法TryGetAuthorizationCode試著從當前請求中提取Authorization Code。在成功得到Authorization Code之后,我們將它作為參數調用Windows Live Connect API獲取相應的Access Token,并調用擴展方法AttachAccessToken將此Access Token附加到當前請求上。
對于另一個實現的ChallengeAsync方法來說,如果通過調用擴展方法TryGetAccessToken不能從當前請求中得到相應的Access Token,我們通過為當前HttpAuthenticationChallengeContext的Result屬性設置一個RedirectResult對象實現了重定向。重定向的地址正是一個用于獲取Authorization Code的URL(“?response_type=code”),當前請求的URI作為其redirect_uri參數。
如下所示的上面提及的針對HttpRequestMessage類型的3個擴展方法的定義。方法TryGetAuthorizationCode從請求URL的查詢字符串(“code”)中提取Authorization Code;方法AttachAccessToken將Access Token添加到請求的屬性字典中;TryGetAccessToken方法則先后從請求的Cookie和屬性字典中提取Access Token。
1: public static class Extensions
2: {
3: //其他成員
4: public static bool TryGetAuthorizationCode(this HttpRequestMessage request, out string authorizationCode)
5: {
6: authorizationCode = HttpUtility.ParseQueryString(request.RequestUri.Query)["code"];
7: return !string.IsNullOrEmpty(authorizationCode);
8: }
9:
10: public static void AttachAccessToken(this HttpRequestMessage request, string accessToken)
11: {
12: string token;
13: if (!request.TryGetAccessToken(out token))
14: {
15: request.Properties[AuthenticateAttribute.CookieName] = accessToken;
16: }
17: }
18:
19: public static bool TryGetAccessToken(this HttpRequestMessage request, out string accessToken)
20: {
21: //從請求的Cookie中獲取Access Token
22: accessToken = null;
23: CookieHeaderValue cookieValue = request.Headers.GetCookies(AuthenticateAttribute.CookieName).FirstOrDefault();
24: if (null != cookieValue)
25: {
26: accessToken = cookieValue.Cookies.FirstOrDefault().Value;
27: return true;
28: }
29:
30: //獲取Attach的Access Token
31: object token;
32: if( request.Properties.TryGetValue(AuthenticateAttribute.CookieName, out token))
33: {
34: accessToken = (string)token;
35: return true;
36: }
37: return false;
38: }
39: }
當我們利用瀏覽器第一次調用定義在DemoController的Action方法GetProfile時(假設采用的URI為“https://www.artech.com/webapi/api/demo”),DemoController上的AuthenticateAttribute特性的AuthenticateAsync方法會率先被執行,但是Access Token和Authorization Code均不存在于當前請求之中,所以并不會執行任何操作。接下來ChallengeAsync方法被執行,瀏覽器被重定向到Windows Live Connect的授權頁面(如果當前用戶尚未登錄到Windows Live Connect,在此之前會先被重定向到登錄頁面。如果之前已經完成了授權,授權頁面不會再出現)。
在取得了用戶授權的情況下,授權服務器會生成一個Authorization Code,并將其作為查詢字符串附加到請求提供的重定向地址,然對針對這個URL實施重定向。由于我們設置的重定向地址為“https://www.artech.com/webapi/api/demo”,所以最終進行重定向的目標地址為“https://www.artech.com/webapi/api/demo?code={authorizationcode}”。
毫無疑問,該地址指向的依然是定義在DemoController中的Action方法GetProfile。在此情況下,AuthenticateAttribute的AuthenticateAsync方法再次被執行。此時它依然不能從請求中得到Access Token,但是卻能得到Authorization Code。于是AuthenticateAttribute利用該Authorization Code調用Windows Live Connect API得到Access Token,并將其添加到請求的屬性字典中。
接下來,Action方法GetProfile方法得以執行,它直接從當前請求(實際上是當前請求的屬性字典中)中獲得Access Token,并利用它調用Windows Live Connect API得到當前登錄用戶的個人信息。目標Action方法執行結束之后,AuthenticateAttribute又會將Acess Token添加到當前響應的Cookie集合中,所以瀏覽器在進行Web API調用時會自動將Access Token以Cookie的形式進行發送。
我們提供的這個實例并沒有演示如何獲取Refresh Token以及在Access Token過期的時候利用它來獲取新的Access Token,有興趣的讀者朋友不妨將此功能一并實現在我們自定義的AuthenticateAttribute之中。
[1] 這里介紹的“客戶端應用”是針對OAuth 2.0授權角色而言,表示被授權的客戶端應用。從運行環境來講,這個應用可以運行于單純的客戶端上下文(既包括運行于瀏覽器環境中的Web應用以及在客戶端安裝的各種App),也可以運行于服務器(比如Web應用中運行于Web Server的那部分程序)。
談談基于OAuth 2.0的第三方認證 [上篇]
談談基于OAuth 2.0的第三方認證 [中篇]
談談基于OAuth 2.0的第三方認證 [下篇]
文章列表