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

如右图所示,用户会先被客户端应用重定向到授权服务器(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”按钮完成授权,成功授权之后,这个的授权页面在后续的请求中将不会再出现。

授权服务器在获取用户的授权之后,会生成一个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("https://www.artech.com/webapi/account/capturetoken")]
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格式显示在浏览器上(如果尚未对该应用进行授权,如上图所示的页面会呈现出来),具体的效果如下图所示。

应用在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的第三方认证 [下篇]
从安全的角度来讲,<中篇>介绍的Implicit类型的Authorization Grant存在这样的两个问题:其一,授权服务器没有对客户端应用进行认证,因为获取Access Token的 ...
- 谈谈基于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标准, ...
随机推荐
- UBUNTU 16.04 编译 OPENJDK8
参考了几篇文章,和错误查询,最后总结如下 一.下载 我比较倾向于使用mercurial来获取源代码,虽然你得挑网络稳定的时候更新,但是易更新. 从官网查找一下,可以通过以下步骤完成源代码的下载 1. ...
- eclipse下创建maven项目
1.创建一个Java项目 1)File--->New--->Other--->Maven--->Maven Projet 2)选择maven-archetype-quickst ...
- VMware Workstation 12序列号
VMware Workstation 12序列号:5A02H-AU243-TZJ49-GTC7K-3C61N 就好像之前微软,让大家用盗版一样,这样可以更快的拥有市场占有率.事实上,输入key即可永久 ...
- [LintCode] Best Time to Buy and Sell Stock II 买股票的最佳时间之二
Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...
- (原).NET程序加入多语言包解决方案工具,超级棒
Multi-Language Add-In Version 5.04.0088 for Visual Studio 2013 安装包:http://www.jollans.com/SetupMulti ...
- java的英文词频算法
java实现的英文词频算法,通常是采用单词树来实现的.使用java实现词频统计,为了统计词汇出现频率,最简单的做法是再建立一个map,其中,key是单词,value代表次数.将文章从头读到尾,读到一个 ...
- Android_SQLite数据库增删改查操作
一:什么是SQLite? 在Android平台上,集成了一个嵌入式关系型轻量级的数据库. 二:什么时候用的数据库? 有大量相似机构的数据需要存储时. 三:如何创建一个数据库? 1.创建一个Sqlite ...
- 学习笔记: Delphi之线程类TThread
新的公司接手的第一份工作就是一个多线程计算的小系统.也幸亏最近对线程有了一些学习,这次一接手就起到了作用.但是在实际的开发过程中还是发现了许多的问题,比如挂起与终止的概念都没有弄明白,导致浪费许多的时 ...
- DBImport v3.44 中文版发布:数据库数据互导及文档生成工具(IT人员必备)
前言: 距离上一个版本V3.3版本的文章发布,已经是1年10个月前的事了. 其实版本一直在更新,但也没什么大的功能更新,总体比较稳定,所以也不怎么写文介绍了. 至于工作上的事,之前有半年时间跑去学英语 ...
- 安卓动态调试七种武器之孔雀翎 – Ida Pro
安卓动态调试七种武器之孔雀翎 – Ida Pro 作者:蒸米@阿里聚安全 0x00 序 随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的.另外工具是死的,人是 ...