谈谈基于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:     "":"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: "":"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的第三方认证 [下篇]

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2025-01-20 17:22:59

谈谈基于OAuth 2.0的第三方认证 [下篇]的相关文章

谈谈基于OAuth 2.0的第三方认证 [上篇]

对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负责的认证方式会带来如下两个问题: 对于用户来说,他们不得不针对不同的访问Web应用提供不同的用户凭证.如果这些凭证具有完全不同的密码,我们没有多少人能够记得住,所以对于大部分整天畅游Internet的网友来说,我想他们在不同的网站注册的帐号都会采用相同的密码.密码的共享必然带来安全隐患,因为我们不能

谈谈基于OAuth 2.0的第三方认证 [中篇]

虽然我们在<上篇>分别讨论了4种预定义的Authorization Grant类型以及它们各自的适用场景的获取Access Token的方式,我想很多之前没有接触过OAuth 2.0的读者朋友们依然会有"不值所云" 之感,所以在介绍的内容中,我们将采用实例演示的方式对Implicit和Authorization Code这两种常用的Authorization Grant作深入介绍.本章着重介绍Implicit Authorization Grant. Implicit Au

OAuth 2.0 认证的原理与实践

原文同步至https://waylau.com/principle-and-practice-of-oauth2/ 使用 OAuth 2.0 认证的的好处是显然易见的.你只需要用同一个账号密码,就能在各个网站进行访问,而免去了在每个网站都进行注册的繁琐过程. 本文将介绍 OAuth 2.0 的原理,并基于 Spring Security 和 GitHub 账号,来演示 OAuth 2.0 的认证的过程. 什么是 OAuth 2.0 OAuth 2.0 的规范可以参考 : RFC 6749 OAu

.net-wcf支持第三方授权oauth 2.0吗?

问题描述 wcf支持第三方授权oauth 2.0吗? wcf支持第三方授权oauth 2.0吗?wcf支持第三方授权oauth 2.0吗? 解决方案 支持的https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization 解决方案二: http://stackoverflow.com/questions/15137510/oauth-2-0-integrated-with-rest-wcf-ser

oAuth 2.0 笔记

OAuth 2.0规范于2012年发布,很多大型互联网公司(比如:微信.微博.支付宝)对外提供的SDK中,授权部分基本上都是按这个规范来实现的. OAuth 2.0提供了4种基本的标准授权流程,最为复杂的是Code(授权码)这种类型,流程图如下:(摘自RFC6749官方文档) 上图中有几个术语解释一下: Resource: 受保护的资源,比如:用户abc在微信上的用户资料(头像,朋友圈之类) Resource Owner:资源所有人,即:上面讲的用户abc Client:指第三方应用,比如:微信

理解OAuth 2.0

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版. 本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749. 一.应用场景 为了理解OAuth的适用场合,让我举一个假设的例子. 有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来.用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片. 问题是只有得到用户的授权,Google才

谈谈基于Kerberos的Windows Network Authentication[上篇]

Content: 基本原理 引入Key Distribution: KServer-Client从何而来 引入Authenticator : 为有效的证明自己提供证据 引入Ticket Granting  Service:如何获得Ticket Kerberos的3个Sub-protocol:整个Authentication的流程User2User Protocol: 有效地保障Server的安全 Kerberos的优点的优点 前几天在给人解释Windows是如何通过Kerberos进行Authe

谈谈基于Kerberos的Windows Network Authentication[下篇]

六.User2User Sub-Protocol:有效地保障Server的安全 通过3个Sub-protocol的介绍,我们可以全面地掌握整个Kerberos的认证过程.实际上,在Windows 2000时代,基于Kerberos的Windows Authentication就是按照这样的工作流程来进行的.但是我在上面一节结束的时候也说了,基于3个Sub-protocol的Kerberos作为一种Network Authentication是具有它自己的局限和安全隐患的.我在整篇文章一直在强调这

谈谈基于Kerberos的Windows Network Authentication

前几天在给人解释Windows是如何通过Kerberos进行Authentication的时候,讲了半天也别把那位老兄讲明白,还差点把自己给绕进去.后来想想原因有以下两点:对于一个没有完全不了解Kerberos的人来说,Kerberos的整个Authentication过程确实不好理解--一会儿以这个Key进行加密.一会儿又要以另一个Key进行加密,确实很容易把人给弄晕:另一方面是我讲解方式有问题,一开始就从Kerberos的3个Sub-protocol全面讲述整个Authentication