谈谈基于OAuth 2.0的第三方认证 [下篇]
从安全的角度来讲,《中篇》介绍的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的第三方认证 [中篇]
虽然我们在<上篇>分别讨论了4种预定义的Authorization Grant类型以及它们各自的适用场景的获取Access Token的方式,我想很多之前没有接触过OAuth 2.0的读者 ...
- 谈谈基于OAuth 2.0的第三方认证 [上篇]
对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负 ...
- 基于OAuth 2.0的第三方认证 -戈多编程
引用(http://www.cnblogs.com/artech/p/oauth-01.html) OAuth 2.0的角色 获得资源拥有者授权的第三方应用请求受保护的资源采用的不是授权者的凭证,所有 ...
- 基于OAuth2.0的第三方认证
浅显易懂的解释 来源 yahoo OAuth认证 原理 理解OAuth 2.0:原理.分类 一张图搞定OAuth2.0:是什么,怎么用 应用自身,完成用户认证: 缺点: 1.不同的访问Web应用提供不 ...
- 基于PHP构建OAuth 2.0 服务端 认证平台
OAuth2.0 认证服务 安装 你可以在github上下载OAuth Server PHP,也可以用下列命令下载,不过内容都是一样的 mkdir my-oauth2-walkthrough cd m ...
- 用DotNetOpenAuth实现基于OAuth 2.0的web api授权 (一)Getting Start
1. 下载 源码下载 2. build solution,创建虚拟目录: 右健MyContatacts/MyPromo项目,选择Properties,点击左边的Web,点击 Create Virtua ...
- 微服务安全(二)OAuth 2.0
1. 概念 OAuth是一个开放的.安全的用户认证协议,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源,而无须将用户名和登录口令提供给第三方应用.授权的第三方应用只能在特定的时段内访问特定 ...
- 微服务系列之授权认证(一) OAuth 2.0 和 OpenID Connect
1.传统架构的授权认证 传统应用架构,用户使用账号密码登录后,可以使用前端cookie存储登录状态,也可以使用后端session方式存储登录状态,小应用这么做其实很高效实用,当应用需要横向扩展时,就需 ...
- 一个功能完备的.NET开源OpenID Connect/OAuth 2.0框架——IdentityServer3
今天推荐的是我一直以来都在关注的一个开源的OpenID Connect/OAuth 2.0服务框架--IdentityServer3.其支持完整的OpenID Connect/OAuth 2.0标准, ...
随机推荐
- e.preventDefault() e.stopPropagation()和return false的区别
e.preventDefault(); //阻止事件的默认行为,比如a标签的转向,但不阻止事件的冒泡传播e.stopPropagation() //阻止事件的冒泡传播,但不阻止其默认行为returne ...
- android开发中使不同的listview同时联动
在做一个Android程序时,需要在一个屏幕上显示两个不同的listview,开始用< linearlayout>包裹这两个listview在<ScrollView >设置时, ...
- 基于webdriver的jmeter性能测试-Eclipse+Selenium+JUnit生成jar包
续接 打开eclipse新建java项目,如下图所示: 输入项目名称后点击"完成"按钮,如下图所示: eclipse中新建一个java项目,如下图所示: 添加类库,如下图所示: 在 ...
- PHP去重算法的优化过程
最近公司在做一个项目,需要对爬取到的数据进行去重,方法就是根据数据的id,去除掉id重复的数据. 下面是这个方法的演化过程. // 去重 $arr_id = array(); $LeTVFeedLis ...
- Javascript初学篇章_8(事件)
事件 HTML 事件是发生在 HTML 元素上的事情.例如用户点击按钮时,点击也是一个事件.事件可以用于处理表单验证,用户输入,用户行为及浏览器动作,如: 页面加载时触发事件 页面关闭时触发事件 用户 ...
- Java 8 forEach简单例子
1. forEach and Map 1.1 通常这样遍历一个Map Map<String, Integer> items = new HashMap<>(); items.p ...
- pyserial 16进制显示与发送
pyserial 16进制显示与发送 http://www.centoscn.com/python/2013/0817/1320.html 十六进制显示的实质是把接收到的字符诸葛转换成其对应的ASCI ...
- 用DataGridView导入TXT文件,并导出为XLS文件
使用 DataGridView 控件,可以显示和编辑来自多种不同类型的数据源的表格数据.也可以导出.txt,.xls等格式的文件.今天我们就先介绍一下用DataGridView把导入txt文件,导出x ...
- IEEE/ACM ASONAM 2014 Industry Track Call for Papers
IEEE/ACM International Conference on Advances in Social Network Analysis and Mining (ASONAM) 2014 In ...
- SQL Server表分区的NULL值问题
SQL Server表分区的NULL值问题 SQL Server表分区只支持range分区这一种类型,但是本人觉得已经够用了 虽然MySQL支持四种分区类型:RANGE分区.LIST分区.HASH分区 ...