OAuth2学习及DotNetOpenAuth部分源码研究7419.docx

上传人:jix****n11 文档编号:48203642 上传时间:2022-10-05 格式:DOCX 页数:48 大小:75.28KB
返回 下载 相关 举报
OAuth2学习及DotNetOpenAuth部分源码研究7419.docx_第1页
第1页 / 共48页
OAuth2学习及DotNetOpenAuth部分源码研究7419.docx_第2页
第2页 / 共48页
点击查看更多>>
资源描述

《OAuth2学习及DotNetOpenAuth部分源码研究7419.docx》由会员分享,可在线阅读,更多相关《OAuth2学习及DotNetOpenAuth部分源码研究7419.docx(48页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是一种开放认证协议,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用.数字2表示现在使用第2代协议. 二.OAuth2中的角色 OAuth2有四种角色 resource owner资源所有者:比如twitter用户,他在twitter的数据就是资源,他自己就是这些资源的所有者。 resource server资源服务器:保存资源的服务器,别人要访问受限制的资源就要出示 Access

2、Token(访问令牌)。 client客户端:一个经过授权后,可以代表资源所有者访问资源服务器上受限制资源的一方。比如开发者开发的应用。 authorization server授权服务器:对 资源所有者进行认证,认证通过后,向 客户端发放 Access Token(访问令牌)。 三.认证过程 用户访问客户端的网站,想操作自己存放在资源服务提供方的资源。 客户端将用户引导至授权服务提供方的授权页面请求用户授权,在这个过程中将客户端的回调连接发送给授权服务提供方。 用户在授权服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。 授权成功后,授权服务提供方对客户端授予一个授权码,

3、网站跳回至客户端。 客户端获得授权码后,再次从授权服务提供方请求获取访问令牌 。 授权服务提供方根据授权码授予客户端访问令牌。 客户端使用获取的访问令牌访问存放在资源服务提供方上的受保护的资源。 四.获取访问令牌方式 从上面可以看到,令牌是串起整个认证流程的核心.OAuth2有四种获取令牌的方式 Authorization Code授权码方式:这种是推荐使用的,也是最安全的. Implicit Grant隐式授权:相比授权码授权,隐式授权少了第一步的取Authorization Code的过程,而且不会返回 refresh_token。主要用于无服务器端的应用,比如浏览器插件。 Resourc

4、e Owner Password Credentials资源所有者密码证书授权:这种验证主要用于资源所有者对Client有极高的信任度的情况,比如操作系统或高权限程序。只有在不能使用其它授权方式的情况下才使用这种方式。 Client Credentials客户端证书授权:这种情况下 Client使用自己的 client证书(如 client_id及client_secret组成的 http basic验证码)来获取 access token,只能用于信任的client。 本文主要讲解第一种获取方式. 有能有些人有这样的疑问,为什么授权成功后不直接返回访问令牌,则是获取授权码,然后使用授权码去换

5、访问令牌.这个问题的答案在官方的文档里,原因主要是保障数据安全性.当用户授权成功,浏览器从授权服务器返回客户端时,数据是通过QueryString传递的.如果直接返回访问令牌,则直接在地址栏可见,相关的日志系统也会记录,这会提高令牌被破解的风险.返回授权码,然后客户端通过直接通信使用授权码换取访问令牌,整个过程对用户是不可见的,这样大大提高了安全性. 五.DotNetOpenAuth在OAuth2中的应用 官方Sample内包含有OAuth的完整示例,其授权服务器使用Mvc编写,客户端与资源服务器使用WebForm编写,数据层使用了EF.为了更加贴进实际使用,减少无关杂音,本人模仿其重写了一个

6、Sample,本文的讲解将围绕自行编写的Sample展开.Sample示例可于文后下载. 1.客户端 客户端编程主要围绕三个类展开 AuthorizationServerDescription,顾名思义,用于对服务端的描述.如下所示private static AuthorizationServerDescription AuthServerDescription; private static readonly WebServerClient Client; static OAuth2Client() AuthServerDescription = new AuthorizationServ

7、erDescription(); AuthServerDescription.TokenEndpoint = new Uri(http:/localhost:8301/OAuth/Token); AuthServerDescription.AuthorizationEndpoint = new Uri(http:/localhost:8301/OAuth/Authorize); Client = new WebServerClient(AuthServerDescription, sampleconsumer, samplesecret); 可以看到,主要设置其两个地址:令牌获取地址与授权地址

8、.然后将其作为参数来构建WebServerClient类. WebServerClient类,是OAuth2的客户端代理类,与授权服务器和资源服务器交互的方法都定义在上面.在实例化时需要传入AuthServerDescription对象,客户端名与客户端密码.这对名称与密码应该是事先向授权服务器申请的,用于标识每一个使用数据的客户端.各个客户端拥有各自的名称与密码. 生成客户端代理后,第一件事就是应该访问授权服务器获取授权码.这主要由WebServerClient类的RequestUserAuthorization方法完成.public void RequestUserAuthorizatio

9、n(IEnumerable scope = null, Uri returnTo = null); 在申请授权码时,还会向授权服务器发送申请权限的范围,参数名叫scope.一般都是一个Url地址. 申请成功,授权服务器返回后,客户端需再次访问授权服务器申请访问令牌.这主要由WebServerClient类的ProcessUserAuthorization方法完成public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null); 成功申请后,会返回一个IAuthorizationState接口对

10、象,其定义如下string AccessToken get; set; DateTime? AccessTokenExpirationUtc get; set; DateTime? AccessTokenIssueDateUtc get; set; Uri Callback get; set; string RefreshToken get; set; HashSet Scope get; 很好理解,AccessToken为访问令牌,RefreshToken为刷新令牌,AccessTokenIssueDateUtc为访问令牌生成时间,AccessTokenExpirationUtc为访问令牌过

11、期时间,Callback为回调的Url,Scope为权限的范围,或者叫被授权可以访问的地址范围. 在Sample中为了简化编程对框架作了二次封装,如下1 private static AuthorizationServerDescription AuthServerDescription; 2 3 private static readonly WebServerClient Client; 4 5 static OAuth2Client() 6 7 AuthServerDescription = new AuthorizationServerDescription(); 8 AuthServ

12、erDescription.TokenEndpoint = new Uri(http:/localhost:8301/OAuth/Token); 9 AuthServerDescription.AuthorizationEndpoint = new Uri(http:/localhost:8301/OAuth/Authorize); 10 11 Client = new WebServerClient(AuthServerDescription, sampleconsumer, samplesecret); 12 13 14 private static IAuthorizationState

13、 Authorization 15 16 get return (AuthorizationState)HttpContext.Current.SessionAuthorization; 17 set HttpContext.Current.SessionAuthorization = value; 18 19 20 public static void GetUserAuthorization(string scope) 21 22 GetUserAuthorization(new string scope ); 23 24 25 public static void GetUserAuth

14、orization(IEnumerable scopes) 26 27 if (Authorization != null) 28 29 return; 30 31 32 IAuthorizationState authorization = Client.ProcessUserAuthorization(); 33 if (authorization = null) 34 35 Client.RequestUserAuthorization(scopes); 36 37 return; 38 39 40 Authorization = authorization; 41 HttpContex

15、t.Current.Response.Redirect(HttpContext.Current.Request.Path); 42 前12行为对象初始化,14到18行将获取的权限对象保存在Session中,属性名为Authorization.客户端使用GetUserAuthorization方法来获取对某地址访问授权. 在页面中调用代码如下if (!IsPostBack) OAuth2Client.GetUserAuthorization(http:/tempuri.org/IGetData/NameLength); 打开页面,首次调用GetUserAuthorization方法后,首先判断权

16、限对象Authorization是否为空.不为空说明已获取到权限.为空则执行ProcessUserAuthorization方法获取访问令牌,由于此时没有授权码,则返回的权限对象为空.最后通过RequestUserAuthorization方法向授权服务器申请授权码. 获取成功后,浏览器页面会刷新,在页面地址后追加了授权码.此时第二次执行GetUserAuthorization方法.权限对象Authorization仍然为空,但由于已有授权码,则ProcessUserAuthorization方法将向授权服务器申请访问令牌.获取成功后将返回的权限对象赋给Authorization属性,然后再次

17、刷新本页面.注意,刷新地址使用的是HttpContext.Current.Request.Path,而此属性是不包括QueryString的.作用是将授权码从地址栏中去除. 第三次执行GetUserAuthorization方法,由于权限对象Authorization已不为空,则直接返回. 访问令牌默认是有时效的.当过期后,要么走上面三步重新申请一个令牌,不过更好的方法是使用刷新令牌刷新访问令牌.这主要由WebServerClient类的RefreshAuthorization方法完成public bool RefreshAuthorization(IAuthorizationState au

18、thorization, TimeSpan? skipIfUsefulLifeExceeds = null); 使用访问令牌的方式,是将令牌添加到访问资源服务器Http请求的头上,这主要由WebServerClient类的AuthorizeRequest方法完成public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization); public void AuthorizeRequest(WebHeaderCollection requestHeaders, IAuthorizationS

19、tate authorization); 在Sample中针对Wcf请求作了二次封装,如下1 public static TReturn UseService(ExpressionFuncoperation) 2 3 if (Authorization.AccessTokenExpirationUtc.HasValue) 4 5 Client.RefreshAuthorization(Authorization, TimeSpan.FromMinutes(2); 6 7 8 TService channel = new ChannelFactory(*).CreateChannel(); 9

20、IClientChannel client = (IClientChannel)channel; 10 11 HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(client.RemoteAddress.Uri); 12 ClientBase.AuthorizeRequest(httpRequest, Authorization.AccessToken); 13 HttpRequestMessageProperty httpDetails = new HttpRequestMessageProperty(); 14 ht

21、tpDetails.HeadersHttpRequestHeader.Authorization = httpRequest.HeadersHttpRequestHeader.Authorization; 15 16 using (OperationContextScope scope = new OperationContextScope(client) 17 18 OperationContext.Current.OutgoingMessagePropertiesHttpRequestMessageProperty.Name = httpDetails; 19 20 client.Open

22、(); 21 TReturn result = operation.Compile().Invoke(channel); 22 try 23 24 client.Close(); 25 26 catch 27 28 client.Abort(); 29 throw; 30 31 32 return result; 33 34 在请求一个Wcf前,首先判断有效期.如果少于2分钟则首先刷新访问令牌.之后构建一个HttpWebRequest对象,并使用AuthorizeRequest方法将访问令牌添加在请求头上.从第13行之后是Wcf的特定写法,其中13到18行表示将Http授权头赋给Wcf授权头.

23、 2.授权服务端 服务端要做的事其实很好理解,就是记录某用户在某客户端的授权情况.其使用数据库来保存相关信息.Client表存储客户端,User表存储用户,ClientAuthorization表是张关系表,存储某用户在某客户端授予的权限.Nonce存储访问随机数,SymmertricCryptoKey表存储对称加密的密码. 服务端主要围绕以下对象编程 AuthorizationServer类,代表授权服务类.主要的功能都由它提供.IAuthorizationServerHost接口是编写验证逻辑的地方,由OAuth2AuthorizationServer类实现,ICryptoKeyStore

24、是访问密码的接口,INonceStore是访问随机数的地方,这两个接口由DatabaseKeyNonceStore类实现,IClientDescription是描述客户端的接口,由Client实现. 在本Sample中,OpenId与OAuth2是配合使用的.用户需要先去OpenId进行登录,然后去OAuth2进行授权.从这个意义上讲,OAuth2受OpenId的统一管理,是其一个客户端. AccountController是一个典型的OpenId客户端编程.上篇文章已有讲解,故不赘述. 当客户端申请授权码时,首先执行OAuthController类的Authorize方法,如下,有删节pub

25、lic ActionResult Authorize() var pendingRequest = this.authorizationServer.ReadAuthorizationRequest(); if (this.authorizationServer.AuthorizationServerServices as OAuth2AuthorizationServer).CanBeAutoApproved(pendingRequest) var approval = this.authorizationServer.PrepareApproveAuthorizationRequest(p

26、endingRequest, HttpContext.User.Identity.Name); return this.authorizationServer.Channel.PrepareResponse(approval).AsActionResult(); database.AddParameter(ClientIdentifier, pendingRequest.ClientIdentifier); ViewBag.Name = database.ExecuteScalar(select name from Client where ClientIdentifier = ClientI

27、dentifier).ToString(); ViewBag.AuthorizationRequest = pendingRequest; return View(); AuthorizationServer类的ReadAuthorizationRequest方法会获取用户请求并返回一个EndUserAuthorizationRequest对象,此对象定义如下public Uri Callback get; set; public string ClientIdentifier get; set; public string ClientState get; set; public virtu

28、al EndUserAuthorizationResponseType ResponseType get; public HashSet Scope get; 可以看到包括了客户端的相关信息.然后将此对象传入OAuth2AuthorizationServer对像的CanBeAutoApproved方法,查看能否自动发放授权码.public bool CanBeAutoApproved(EndUserAuthorizationRequest authorizationRequest) if (authorizationRequest.ResponseType = EndUserAuthoriza

29、tionResponseType.AuthorizationCode) database.AddParameter(ClientIdentifier, authorizationRequest.ClientIdentifier); object result = database.ExecuteScalar(select ClientSecret from client where ClientIdentifier = ClientIdentifier); if (result != null & !string.IsNullOrEmpty(result.ToString() return t

30、his.IsAuthorizationValid(authorizationRequest.Scope, authorizationRequest.ClientIdentifier, DateTime.UtcNow, HttpContext.Current.User.Identity.Name); return false; 此方法是查找数据库中有无此客户端记录且密码不为空,如果不为空且处于获取授权码阶段,则会调用了IsAuthorizationValid方法private bool IsAuthorizationValid(HashSet requestedScopes, string cl

31、ientIdentifier, DateTime issuedUtc, string username) issuedUtc += TimeSpan.FromSeconds(1); database.AddParameter(ClientIdentifier, clientIdentifier); database.AddParameter(CreatedOnUtc, issuedUtc); database.AddParameter(ExpirationDateUtc, DateTime.UtcNow); database.AddParameter(OpenIDClaimedIdentifi

32、er, username); StringBuilder sb = new StringBuilder(); sb.Append(select scope from user u ); sb.Append( join ClientAuthorization ca on u.userid = ca.userid ); sb.Append( join Client c on c.clientid = ca.clientid ); sb.Append( where c.ClientIdentifier = ClientIdentifier ); sb.Append( and CreatedOnUtc

33、 = ExpirationDateUtc ) ); sb.Append( and u.OpenIDClaimedIdentifier = OpenIDClaimedIdentifier ); DataTable dt = database.ExecuteDataSet(sb.ToString().Tables0; if (dt.Rows.Count = 0) return false; var grantedScopes = new HashSet(OAuthUtilities.ScopeStringComparer); foreach (DataRow dr in dt.Rows) gran

34、tedScopes.UnionWith(OAuthUtilities.SplitScopes(drscope.ToString(); return requestedScopes.IsSubsetOf(grantedScopes); 可以看到,此方法查找指定用户在指定客户端上是否有对目标范围的授权,且没有过期.也就是说,如果客服端的密码不能为空,且当前用户在此客户端上对目标范围还有未过期的授权,则自动发放授权码. 回到最初的Authorize方法.如果可以自动发放授权码,则调用AuthorizationServer类的PrepareApproveAuthorizationRequest方法生成

35、一个授权码,并通过AuthorizationServer类Channel属性的PrepareResponse方法最终返回给客户端. 如果不能自动发放,则浏览器会跳转到一个确认页面,如下图所示 点击后执行OAuthController类的AuthorizeResponse方法,有删节.public ActionResult AuthorizeResponse(bool isApproved) var pendingRequest = this.authorizationServer.ReadAuthorizationRequest(); IDirectedProtocolMessage resp

36、onse; if (isApproved) database.AddParameter(ClientIdentifier, pendingRequest.ClientIdentifier); int clientId = Convert.ToInt32(database.ExecuteScalar(select clientId from client where ClientIdentifier = ClientIdentifier); database.AddParameter(OpenIDClaimedIdentifier, User.Identity.Name); int userId

37、 = Convert.ToInt32(database.ExecuteScalar(select userId from user where OpenIDClaimedIdentifier = OpenIDClaimedIdentifier); database.AddParameter(CreatedOnUtc, DateTime.UtcNow); database.AddParameter(clientId, clientId); database.AddParameter(userId, userId); database.AddParameter(Scope, OAuthUtilit

38、ies.JoinScopes(pendingRequest.Scope); database.ExecuteNonQuery(insert into ClientAuthorization values(null, CreatedOnUtc, clientId, userId, Scope, null); response = this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest, User.Identity.Name); else response = this.authorizationServ

39、er.PrepareRejectAuthorizationRequest(pendingRequest); return this.authorizationServer.Channel.PrepareResponse(response).AsActionResult(); 逻辑比较简单,如果同意,则获取客户端信息后,在数据库的ClientAuthorization表中插入某时某用户在某客户端对于某访问范围的权限信息,然后如同上面一样,调用AuthorizationServer类的PrepareApproveAuthorizationRequest方法生成一个授权码,并通过Authorizat

40、ionServer类Channel属性的PrepareResponse方法最终返回给客户端. 有一点需要注意.Authorize方法是从请求中获取客户端信息,而AuthorizeResponse方法则是从Authorize方法所对应的View中获取客户端信息.所以此View必需包含相关系统.在Sample中我首先将获取出来的pendingRequest对象赋于ViewBag.AuthorizationRequest,然后在View中将其放入隐藏域.注意,其名字是固定的. ViewBag.Title = Authorize; Layout = /Views/Shared/_Layout.csht

41、ml; DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationRequest AuthorizationRequest = ViewBag.AuthorizationRequest; Authorize 是否授权 ViewBag.Name 访问以下地址 foreach (string scope in AuthorizationRequest.Scope) scope using (Html.BeginForm(AuthorizeResponse, OAuth) Html.AntiForgeryToken() Html.Hidden(isAppr

42、oved) Html.Hidden(client_id, AuthorizationRequest.ClientIdentifier) Html.Hidden(redirect_uri, AuthorizationRequest.Callback) Html.Hidden(state, AuthorizationRequest.ClientState) Html.Hidden(scope, DotNetOpenAuth.OAuth2.OAuthUtilities.JoinScopes(AuthorizationRequest.Scope) Html.Hidden(response_type,

43、AuthorizationRequest.ResponseType = DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationResponseType.AccessToken ? token : code) 此时客户端已获取到授权码.然后会发出第二次请求申请访问令牌.这个请求由OAuthController类的Token方法处理public ActionResult Token() return this.authorizationServer.HandleTokenRequest(this.Request).AsActionResult();

44、实际上由AuthorizationServer类的HandleTokenRequest方法处理,最终调用OAuth2AuthorizationServer类的CreateAccessToken方法创建访问令牌并返回客户端. 大体的服务端编程接口分析到此结束,下面我们深入源码来理解这些关键类的架构模式. AuthorizationServer类主要提供编程接口,而自行实现的OAuth2AuthorizationServer类,DatabaseKeyNonceStore类和Client类则主要负责与数据库的交互.真正负责通信的是Channel抽象类,其作为AuthorizationServer类的

45、Channel属性对外公布,具体实现类为OAuth2AuthorizationServerChannel类. 在Channel类上作者使用了一种类似于Asp.Net的管道模型的方式来架构此类.相对于IHttpModule接口,这里的接口名叫IChannelBindingElement.其定义如下public interface IChannelBindingElement Channel Channel get; set; MessageProtections Protection get; MessageProtections? ProcessOutgoingMessage(IProtocolMessage message); MessageProtections? ProcessIncomingMessage(IProtocolMessage message); 而在Channel类中的关键部分如下private readonly ListIChannelBind

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 技术总结

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com