开发者社区> 行者武松> 正文

谈谈基于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并最终获取到受保护资源(登录用户个人信息)的完整流程。

20083441-a4f9be6c332245f89ee79d2087f2d3e

如右图所示,用户会先被客户端应用重定向到授权服务器(login.live.com),具体的地址为“https://login.live.com/oauth20_authorize.srf”。相关的输入参数通过查询字符串的形式,必须提供的参数包含在如下的列表中。

  • 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”按钮完成授权,成功授权之后,这个的授权页面在后续的请求中将不会再出现。

20082459-ea632fe93e1c48df93001783666fb02

授权服务器在获取用户的授权之后,会生成一个Access Token。接下来,它会提取请求中指定的重定向地址(即redirect_uri参数),然后将生成的Access Token以Hash(#)的形式附加在该地址后面,最终针对这个携带有Access Token的新地址返回一个重定向的响应。如第一张图所示,我们采用的重定向地址为“http://www.myapp.com/capturetoken”,那么最终浏览器将会重定向到地址“http://www.myapp.com/capturetoken#acess_token={accesstoken}”上。

这个重定向地址对应着客户端应用需要获取授权资源的页面,该页面可以直接从代表当前地址的URL中获得Access Token,并利用它来获取目标资源。对于我们的例子来说,它需要获取当前Windows Live帐号的基本信息,请求的地址为“https://apis.live.net/v5.0/me”,Access Token以查询字符串的形式(“?access_token={accesstoken}”)提供给资源服务器,后者据此验证请求的合法性并在验证成功的情况下将当前用户的基本信息以JSON的形式返回给客户端应用。

实例演示:创建采用Implicit Authorization Grant的Web API应用

接下来我们创建一个ASP.NET Web API程序来实现上面这个应用场景。我们首先需要按照《上篇》介绍的流程为该应用注册一个ClientID,如果我们已经在Windows Live Connect上创建了一个应用,我们可以直接使用该应用的ClientID。

假设我们在Windows Live Connect创建了一个采用“https://www.artech.com”作为域名的应用,我们需要利用hosts文件(“%windir%\System32\drivers\etc\hosts”)将此域名映射为本机的IP地址(127.0.0.1),具体的映射脚本如下所示。除此之外,由于我们采用HTTPS并且采用本地IIS作为宿主,所以我们需要为Web API应用所在的站点添加一个HTTPS绑定。

   1: 127.0.0.1 www.artech.com

在具体介绍认证实现原理之前,我们不妨先来演示一下最终达到的效果。我们在ASP.NET Web API应用中定义了如下一个继承自ApiController的DemoController,它具有唯一一个用于获取当前登录用户个人基本信息的Action方法GetProfile。在该方法中,它通过我们定义的扩展方法TryGetAccessToken从当前请求中提取Access Token,然后利用它调用Windows Live Connect提供的Web API(https://apis.live.net/v5.0/me)。

   1: [Authenticate()] 
   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("https://apis.live.net/v5.0/me?access_token={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格式显示在浏览器上(如果尚未对该应用进行授权,如上图所示的页面会呈现出来),具体的效果如下图所示。

20082503-0a92f01256a748d897745647cbb6f3b

应用在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<object>(null);
  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 = "https://login.live.com/oauth20_authorize.srf";
  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<object>(null);
  33:     }
  34:  
  35:     public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> 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<HttpResponseMessage>(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://www.artech.com/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: <html>
   2:     <head>
   3:         <script src="@Url.Content("~/scripts/jquery-1.10.2.js")"></script>
   4:         <script type="text/javascript">
   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:         </script>
  16:     </head>
  17: </html>

上面的代码片断代表Action方法CaptureToken对应View的定义。在该View中,我们从当前地址的Hash(#)部分得到Access Token,并将其作为查询字符串附加到从ViewBag中得到的资源访问地址上,并通过设置location的href属性的方式携带Access Token对Web API再次发起调用。


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


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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
C#基础01
C#基础01
3 0
VS2013发布网站
VS2013发布网站
3 0
面试官:如何提升应用的 Lighthouse 分数
Lighthouse 是用于测试网页性能工具最有用的技术之一。 它具备了自动化,并可以测量网站的可访问性和 SEO。 更重要的是,它目前是开源的并且可以免费使用。 同时,它可以用来测试渐进式 Web 应用程序。 今天,性能不仅仅是渲染应用程序所需的时间。 一些不良的体验带来的多米诺骨牌效应更会损害你的应用程序。
3 0
WinForm,MVC知识点
WinForm,MVC知识点
3 0
ADO.NET基础01
ADO.NET基础01
3 0
MVC知识点02
MVC知识点02
3 0
MySQL:5.6 大事务show engine innodb status故障一例
MySQL:5.6 大事务show engine innodb status故障一例
4 0
把bthread_start_background封装成现代C++的风格!
在基于brpc开发服务的时候,bthread_start_background()一定是高频函数。bthread_start_background()是brpc框架提供给我们的API,让我们可以方便使用brpc的协程bthread。
4 0
百度工程师厂外生存指南
百度曾经一度被称为中国互联网的黄埔军校。这句话其实有两方面含义:一是说从百度走出来的工程师活跃在中国各大互联网企业中,对整个中国互联网的繁荣发展做出了贡献。二是说百度如同历史上的黄埔军校一般,为外界培育和输送了大量人才,但是自身却在逐步没落,暗示百度的人才流失严重。然而很多百度厂内高管常以『百度是中国互联网的黄埔军校』而自豪,这只是理解了这句话的第一层含义,却殊不知其第二层。高管们不对厂内人才大量流失的原因做反思,反而因为一句黄埔军校而沾沾自喜。着实让人唏嘘不已。
4 0
用动画的方式理解事件循环机制,没有搞懂的快来看看
事件循环是每个 JavaScript 开发人员都必须理解的知识点之一,但起初理解起来可能有点困难。 这篇开始,我会尝试通过低分辨率 gif 动画的方式解释它,进而来帮助你理解。
4 0
+关注
行者武松
杀人者,打虎武松也。
17141
文章
2569
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载