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

转载博客,地址:http://www.cnblogs.com/artech/p/oauth-03.html

时间: 2024-10-10 09:25:06

谈谈基于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 Authorizatio

基于OAuth 2.0的第三方授权

OAuth 2.0 什么是OAuth OAuth是Open Authorization的缩写,为用户资源的授权提供了一个安全的.开放而又简易的标准. 在用户授权的前提下允许第三方网站访问用户在服务商里存储信息. 而这种授权无需将用户提供用户名和密码提供给该第三方网站. 提供一个令牌给第三方网站,一个令牌对应一个特定的第三方网站,同时该令牌只能在特定的时间内访问特定的资源. 值得注意的是OAuth 2.0虽然是OAuth 1.0的下一个版本,但并不向后兼容OAuth 1.0. OAuth 2.0授

一个功能完备的.NET开源OpenID Connect/OAuth 2.0框架&mdash;&mdash;IdentityServer3

今天推荐的是我一直以来都在关注的一个开源的OpenID Connect/OAuth 2.0服务框架--IdentityServer3.其支持完整的OpenID Connect/OAuth 2.0标准,使用它就可以轻易地搭建一个单点登录服务器. 说是一直关注,是因为1年前,要为一个平台搭建一个OAuth 2.0服务器,当时由于IdentityServer3还处于开发阶段,核心还不稳定,扩展功能也不完备.无奈只好熟读OAuth 2.0的规范,并根据www.asp.net网站上的一个简单示例自己实现了

构建微服务-使用OAuth 2.0保护API接口

微服务操作模型 基于Spring Cloud和Netflix OSS 构建微服务-Part 1 基于Spring Cloud和Netflix OSS构建微服务,Part 2 在本文中,我们将使用OAuth 2.0,创建一个的安全API,可供外部访问Part 1和Part 2完成的微服务. 关于OAuth 2.0的更多信息,可以访问介绍文档:Parecki - OAuth 2 Simplified 和 Jenkov - OAuth 2.0 Tutorial ,或者规范文档 IETF RFC 674

OAuth 2.0: Bearer Token Usage

Bearer Token (RFC 6750) 用于HTTP请求授权访问OAuth 2.0资源,任何Bearer持有者都可以无差别地用它来访问相关的资源,而无需证明持有加密key.一个Bearer代表授权范围.有效期,以及其他授权事项:一个Bearer在存储和传输过程中应当防止泄露,须使用Transport Layer Security (TLS):一个Bearer有效期通常很短,最好不超过1小时. 一. 资源请求 Bearer实现资源请求有三种方式:Authorization Header.F

基于Thinkphp3.2的qq第三方oauth认证登录扩展类

基于Thinkphp3.2的qq第三方oauth认证登录扩展类,由于腾讯oauth sdk写的太多,不能与thinkphp和好的结合,最终想法讲腾讯oauth sdk写成tp的扩展类先看代码,将代码保存在/library/org/util/Qqconnect.class.php文件中在__construct方法中你可以直接写你的app_id.app_key和回调地址也可以根据自己的喜好,改一下代码传参或者写到配置文件.调用方法:1. 在qq的登录按钮的方法中调用getAuthCode方法例如:$

OAuth 2.0 认证的原理与实践

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

认证授权那点事儿 —— OAuth 2.0

OAuth 2.0 —— 开放授权协议,对应的规范文件RFC-6749早在2012年便成形,所以这并不是一个新的技术(你问我为啥研究这个,我也想吟一首诗啊...组织上就是这样决定的),但由于其必不可少的价值,在今天的网络上已经得到了广泛的应用. OAuth2.0认证是要在不同的应用之间打通互信,互信的目的是为了实现一定程度上的用户数据分享,没数据的一方到有数据的一方拿数据,并且在时间尺度上,数据的分享是受控的. 比如你在微信上打开小程序,小程序会向微信索要你的基本信息:比如你用马克飞象作为印象笔