随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构)、服务器与多种客户端的(如PC、移动、Web等),甚至还有须要以服务的形式开放给第三方的,身份验证这一功能已经演化为一个服务,不少大型应用中都有本身的身份验证服务器甚至集群,因此普通的身份验证方式已经不能知足需求。html
在.Net领域中也有一些开源的身份验证服务器组件,如IdentityServer(http://identityserver.io/),可是这些组件对于一些规模较小的项目来讲可能会感受到比较庞大,增长了学习和维护成本,因此本章将对OAuth以及如何使用OAuth实现身份验证模式进行介绍。
本章的主要内容有:angularjs
● OAuth2.0简介
● 在.Net中使用OAuth实现基于受权码模式的身份验证
● 实现基于Access Token的身份验证
● 加入Refresh Token支持
● 实现经过用户密码模式获取Access Token
● 实现客户端模式获取Access Token
● 关于.Net中OAuth相关令牌的加密说明web
注:本章内容源码下载:https://files.cnblogs.com/files/selimsong/OAuth2Demo.zip数据库
在文章的开始的时候说过现代软件应用的身份验证场景愈来愈丰富,下图是现代应用程序的一个通讯图,它描述了常见的“客户端”是如何与服务器提供的服务通讯的。api
该图出自IdentityServer:https://identityserver4.readthedocs.io/en/release/intro/big_picture.html
为了知足这些场景人们制定了一套标准协议,这个协议就是OAuth(Open Authorization,开放受权)协议,OAuth可以让第三方应用程序去访问受限制的HTTP服务。OAuth有两个版本分别是1.0和2.0,可是因为1.0版本过于复杂因此1.0版本被2.0版本替换了,而且两个版本是不兼容的。
接下来就对OAuth2.0相关的概念进行介绍:浏览器
● Resource Owner:资源拥有者,就是可以访问被限制资源的用户(注:这里的用户是个泛指,它既能够是真实用户也能够是服务程序)。
● Resource Server:资源宿主,可以接受和处理,使用访问令牌(access token)访问受保护资源的请求(如提供API的服务器)。
● Client:它泛指全部的第三方程序(不管是Web应用、桌面应用仍是服务端应用),它经过资源拥有者以及它的受权来访问受保护的资源。
● Authorization Server:用来对受权成功的客户端发布令牌以及对令牌的验证受权。并对Client进行管理。安全
A. 第三方程序向资源拥有者(用户)发送受权请求,这个过程既能够经过客户端直接向用户请求,也能够经过受权服务器做为中介来完成请求。(注:对于受权请求这个概念至关于用户登陆,应用程序能够直接显示一个登陆页面,也能够跳转到验证服务器的统一登陆页面)
B. 用户将受权相关信息“提交”给第三方程序,在OAuth中有4种不一样的权限授予方式,每种方式须要的数据不一样,如基于用户密码的受权方式就须要用户名和密码。
C. 第三方程序将用户的受权信息提交到受权服务器,请求一个Access Token。
D. 受权服务器验证完成用户的受权信息后,将Access Token发放到第三方程序。
E. 第三方程序携带Access Token访问被保护的资源。
F. 资源服务器验证Access Token有效后,将资源返回到第三方程序。服务器
● Authorization Code(受权码模式):该模式的核心是客户端经过一个受权码来向受权服务器申请Access Token。是一种基于重定向的受权模式,受权服务器做为用户和第三方应用(Client)的中介,当用户访问第三方应用是,第三方应用跳转到受权服务器引导用户完成身份验证,生成Authorization Code并转交到第三方应用,以便于第三方应用根据这个受权码完成后续的Access Token获取。
● Implicit(简化模式):简化模式是一种简化的受权码模式,受权码模式在首次访问第三方应用时跳转到受权服务器进行身份验证返回受权码,而简化模式在跳转到受权服务器后直接返回Access Token,这种模式减小了获取Access Token的请求次数。
● Resource Owner Password Credentials(用户密码模式):经过资源拥有者(用户)的用户名和密码来直接获取Access Token的一种方法,这种方法要求第三方应用(Client)是高度可信任的,而且其它受权方式不可用的状况下使用。
● Client Credentials(客户端模式):该模式是经过第三方应用(Client)发送一个本身的凭证到受权服务器得到Access Token,这种模式的使用要求该Client已经被受权服务器管理并限制其对被保护资源的访问范围。另外这种模式下Client应该就是一个资源拥有者(用户),如微服务程序。架构
这个很好理解,第三方应用经过Access Token去获取受保护的资源,可是Access Token是存在有效期的,一旦过时就没法使用,为了不Access Token过时后没法使用,因此加入了Refresh Token的概念,经过刷新的方式来完成Access Token的更新。app
在OAuth2.0中,全部须要访问受限资源的程序都视为第三方应用(Client),为了保证这个Client是安全的、可信任的,因此OAuth须要对Client进行管理。参考:https://tools.ietf.org/html/rfc6749#section-2
这里终结点表明的是HTTP资源,在OAuth受权过程当中须要使用到一些终结点的支持,如Authorization code(受权码)的获取,以及Access Token的获取,终结点由受权服务器提供。参考:https://tools.ietf.org/html/rfc6749#section-3
Access Token的类型是让Client根据具体类型来使用Access Token完成对受保护资源的请求。
OAuth2.0中有两种类型分别是Bearer和Mac,它们体现方式以下:
● Bearer:
● Mac:
参考:https://tools.ietf.org/html/rfc6750
OAuth2.0是一个开放标准,既然是标准那么就能够有实现,在.Net中微软基于Owin实现了OAuth2.0协议,下面就介绍如何在ASP.NET MVC程序中实现OAuth身份验证。
注:本例基于ASP.NET MVC默认带身份验证模板完成。
经过NuGet安装Microsoft.Owin.Security.OAuth组件:
注:从该组件的名称能够看出,.Net对OAuth的实现其实是基于Owin的,因此不少内容均使用Owin中相关的身份验证概念,这些内容可参考本系列与身份验证的文章。
根据上面OAuth的介绍可知,受权服务器是OAuth其中一个角色,该角色最主要的功能就是Access Token的发放以及受权,另外它还用于支持受权码模式的受权码发放以及Client的管理。
在Startup类型的Configuration方法中加入如下代码,该代码是为Owin中间件添加一个受权服务器(注:该中间件是一个Owin的身份验证中间件可参考《ASP.NET没有魔法——ASP.NET Identity 的“多重”身份验证》)。
其中OAuthAuthorizationServerOptions定义以下:
上面的定义能够分为如下几类:
● 终结点地址:AuthorizeEndpointPath、TokenEndpointPath等,它定义了访问获取受权码以及获取Token的地址信息。
● Token提供器:AuthorizationCodeProvider、AccessTokenProvider、RefreshTokenProvider负责完成对应令牌的建立和处理功能。
● Token的“加密”与“解密”:该功能是OAuth与Owin身份验证的结合,经过AccessTokenFormat等ISecureDataFormat接口的实现能够将对应的Token转换成一个 AuthenticationTicket。可参考《ASP.NET没有魔法——ASP.NET Identity的加密与解密》文中TicketDataFormat的用法。
● OAuth受权服务:Provider是整个OAuth服务器的核心,它包含了终结点的处理与响应、OAuth中的4种Access Token受权方式和刷新令牌获取Access Token的方式以及请求、客户端的相关验证:
上面介绍OAuth时介绍了终结点实际上就是用来获取受权码或者Access Token的,在.Net中使用Microsoft.Owin.Security.OAuth组件仅须要经过配置的形式就能够指定受权码及Token获取的终结点访问地址(注:把AllowInsecureHttp配置属性设为true,能够容许不安全的http来访问终结点,该配置仅用于开发环境):
完成后就能够经过浏览器访问这两个地址:
能够看到是能够访问,只不过是有错误的(注:请求地址的QueryString的参数参考文档)。
Client在OAuth中指代了全部的第三方须要访问受限制资源的应用程序,受权服务器为了可以识别和验证Client因此须要完成Client的管理以及验证功能。(注:微软在Microsoft.Owin.Security.OAuth组件中仅仅提供了Client验证的接口,因此要本身实现Client数据的管理以及验证逻辑):
1). 添加Client实体以及对应的仓储(本例之内存的方式实现仓储,实际使用中至少应该保存数据库):
上图是Client最基础的属性(注:若是还须要对Client的访问范围进行限制,那么还应该加入一个Scope的列表,本例再也不加入Scope的限制)。
2). Client的仓储:
3). 实现受权服务器对Client的验证:
因为受权服务器对客户端验证的接口位于OAuthAuthorizationServerProvider类型中,因此首先要继承该类型,并重载相应的验证方法:
上面代码作了如下几件事:
● 尝试从Http请求header或者请求body中获取Client信息,包含Id和密码。
● 若是没有Client的Id信息,那么直接判断为不经过验证,若是有Client的密码信息则保存到Owin上下文中,供后续处理使用。
● 使用得到的ClientId在Client仓储中查询,判断是不是一个合法的Client,如不是则判断为不经过验证。
4). 验证完成后设置该Client的重定向Url(注:该方法仍旧是重载OAuthAuthorizationServerProvider类型中的方法):
受权码的生成是受权服务器终结点的一项功能,当使用受权码模式时,用户访问Client会被引导跳转到受权服务器完成身份验证(登陆),随后又携带受权码跳转回Client,Client使用该受权码获取Access Token。在OAuth的.Net实现中,须要经过在配置中配置一个类型为IAuthenticationTokenProvider的令牌提供器,该提供器用于建立和解析令牌,这里的建立实际就是用户完成登陆后受权码的生成以及受权码和用户登陆身份信息的关联,而解析实际就是根据受权码得到对应用户身份信息并生成Access Token的过程。
下面就经过实现IAuthenticationTokenProvider的方式实现一个自定义受权码提供器:
从上面代码能够看出这个提供器的核心功能是以Guid的方式生成一个键值(受权码)保存了当前用户的信息,当解析时经过该键值(即受权码)获取用户身份信息。(注:AuthenticationTokenCreateContext对象用于对当前用户身份信息AuthenticationTicket对的的序列化和反序列化)
完成后将该提供器配置到受权服务器中间件中:
当用户访问受权码终结点时理应让用户知道Client须要他的受权,为此在ASP.NET MVC程序中须要添加一个路由与受权码终结点地址匹配的Controller、Action以及View:
1). Controller及Action(注:该Action须要经过身份验证,若是没有须要跳转到登陆页面完成身份验证后才可访问):
2). View:显示受权提示
1). 访问受权码终结点获取受权码:http://localhost:59273/oauth2/authorize?response_type=code&client_id=test1
因为没有登陆,因此先跳转到登陆页面。
完成登陆后跳转回受权页面:
点击受权按钮后,携带受权码跳转到test1这个client的重定向Url(注:此处test1这个Client设置的Url就是受权服务器自己,因此看上去没有作重定向)
获得受权码后,携带受权码访问Access Token终结点获取Access Token(注:这里使用Chrome浏览器的Postman拓展来实现请求的模拟):
注:上面响应信息中的access_token包含了加密后用户的身份信息,其加密过程可参考基于Cookie的用户信息加密过程。ASP.NET没有魔法——ASP.NET Identity的加密与解密
上面介绍了如何基于受权码模式得到Access Token,接下来将介绍如何使用Access Token来访问受限制的资源(注:本例中的资源服务器与受权服务器位于同一实例中,因此当资源服务器对access token解密时,可以保证与受权服务器用于生成access token所用密钥一致,可以正常解密,这里的Access Token和基于Cookie的身份验证中的身份验证Cookie性质是相同的,都是将用户的身份信息序列化后的加密字符串)
1. 在Startup类中添加基于Bearer的OAuth身份验证中间件:
2. 添加访问受限制的资源:
3. 访问受限资源:
未添加受权信息直接跳转到登陆页面。
添加Access Token后可正常访问资源:
上面使用受权码模式生成的Access Token是存在过时时间的(实际上不管什么方式生成的Access Token都存在过时时间),Token过时后又不可能让用户再受权一次,因此须要使用Refresh Token来按期刷新Access Token,.Net中实现Refresh Token的方式与受权码相似,在生成Refresh Token的同时会关联用户的身份信息,后续可使用这个Refresh Token来生成新的Access Token。
1. 建立Refresh Token提供器(实现方式与受权码提供器基本一致):
2. 为受权服务器配置Refresh Token提供器:
3. 再次获取到受权码后,根据该受权码获取Access Token,返回信息中将携带Refresh Token:
4. 根据Refresh Token刷新Access Token:
上面介绍了受权码模式的实现方式,但这种方式的核心其实是创建了一个受权码和用户信息的映射(包括刷新令牌方式也是创建了刷新令牌与用户信息的映射),后续的Access Token其实是使用这个了用户信息生成的。换句话用户信息才是核心,.Net中用户信息的体现从底到高分别是:IIdentity->ClaimsIdentity-> AuthenticationTicket,关于用户的身份信息可参考:《ASP.NET没有魔法——ASP.NET Identity与受权》,在基于受权码的模式时经过在受权服务器的登陆功能得到了用户信息,而基于用户名密码模式时没有这个跳转登陆环节,因此须要直接经过用户名密码来获取用户信息,其实现以下重载了OAuthAuthorizationServerProvider类型的GrantResourceOwnerCredentials方法:
该方法从Owin环境中获取Identity中的UserManager对象,经过UserManager来验证用户是否存在,若是存在则将使用用户信息来建立一个ClaimsIdentity对象(注:此处是省略的实现,正常实现可根据需求参考Cookie验证方式将Scope或者Role等信息也添加到Identity对象中)。另UserManager是经过如下代码添加到Owin上下文中的,它的Key值是"AspNet.Identity.Owin:" + typeof(ApplicationUserManager).AssemblyQualifiedName。
使用用户名密码获取Access Token:
客户端模式和用户名密码模式是相似的,它是经过Client的Id以及密码来进行受权,使用的是Client相关的信息,它的实现方式以下,重载GrantClientCredentials方法,经过客户端验证后的id和密码信息来验证改Client是否合法,对于合法的Client为其建立Identity对象(注:此处能够根据实际需求在Identity中添加相应的属性):
使用Client信息获取Access Token:
以上就是.Net中对于OAuth的实现,另外.Net中没有提供简化模式的接口,可是提供了一个GrantCustomExtension,也就是说受权模式是可拓展的。
本例中除了受权码以及刷新令牌是2个Guid链接外,访问令牌(包括全部受权模式生成的令牌)以及受权码对应的用户信息、刷新令牌对应的用户信息都是通过加密的,其加解密对象建立过程以下,具体内容可参考《ASP.NET没有魔法——ASP.NET Identity的加密与解密》
本章内容介绍了OAuth2.0协议相关的内容,并经过一个ASP.NET MVC程序基于微软的Microsoft.Owin.Security.OAuth组件实现了该协议中的大部分功能。使用OAuth来实现身份验证可让咱们的应用程序从Web拓展至任意的平台上运行,但这样的实现仍旧是存在一些问题的,在下一篇文章中将对这些问题进一步的讨论和介绍。
PS.这一章内容比较多,若有问题能够在评论区留言,另外最近事情比较多,因此更新慢了,感谢你们的支持。
参考:
https://stackoverflow.com/questions/39909419/jwt-vs-oauth-authentication
http://www.cnblogs.com/linianhui/p/oauth2-authorization.html
http://www.c-sharpcorner.com/UploadFile/4b0136/openid-connect-availability-in-owin-security-components/
https://docs.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
https://security.stackexchange.com/questions/94995/oauth-2-vs-openid-connect-to-secure-api
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/