将 OWIN 授权服务器与资源服务器解耦





5.00/5 (1投票)
将 OWIN 授权服务器与资源服务器解耦
最近我收到了很多评论和邮件,询问我们如何将我们在之前文章中构建的 OWIN 授权服务器与我们正在保护的资源解耦。如果您正在关注下面提到的文章,您会注意到我们只有一个软件组件(API),它同时扮演授权服务器和资源服务器的角色。
当前的实现并没有错,因为 OAuth 2.0 规范从未强制要求授权服务器和资源服务器之间进行分离,但在许多情况下,特别是当您拥有大量资源和大量需要保护的端点时,从架构角度来看,最好将服务器解耦。这样您最终将拥有一个独立的授权服务器,以及另一个独立的资源服务器,其中包含所有受保护的资源,同时该资源服务器将只理解由授权服务器颁发的访问令牌。
您可以在 Github 上查看源代码。
- 使用 ASP.NET Web API 2、Owin 和 Identity 的基于令牌的身份验证 – 第 1 部分。
- 使用 ASP.NET Web API 2、Owin 和 Identity 的 AngularJS 令牌身份验证 – 第 2 部分。
- 使用 ASP.NET Web API 2 和 Owin 在 AngularJS 应用程序中启用 OAuth 刷新令牌 – 第 3 部分。
- 使用 Facebook 和 Google 在 AngularJS 应用程序中进行 ASP.NET Web API 2 外部登录 – 第 4 部分。
OAuth 2.0 角色:授权服务器、资源服务器、客户端和资源所有者
在我们深入实现以及如何解耦服务器之前,我想强调一下规范中定义的 OAuth 2.0 角色;这样我们就能更好地理解每个角色的职责,通过查看下图,您会注意到涉及 4 个角色
资源所有者
拥有受保护的资源的实体或人员(用户)。
资源服务器
托管受保护资源的服务器,该服务器应能够接受授权服务器颁发的访问令牌,并在访问令牌有效时响应受保护资源。
客户端应用程序
请求访问资源服务器上受保护资源的应用程序或(软件),此客户端(在某些 OAuth 流程中)可以代表资源所有者请求访问令牌。
授权服务器
负责管理授权并在验证资源所有者身份后向客户端颁发访问令牌的服务器。
注意:在我们的架构中,我们可以拥有单个授权服务器,它负责颁发可供多个资源服务器使用的访问令牌。
解耦 OAuth 2.0 OWIN 授权服务器和资源服务器
在之前的文章中,我们使用单个软件组件(API)构建了授权服务器和资源服务器,该 API 可通过 (http://ngauthenticationAPI.azurewebsites.net) 访问,在此演示中,我将指导您如何创建新的独立资源 API 并将其托管在与授权服务器不同的机器上,然后配置资源 API 以仅接受由我们的授权服务器颁发的访问令牌。这个新的资源服务器托管在 Azure 上,可通过 (http://ngauthenticationResourcesAPI.azurewebsites.net) 访问。
注意:正如您从之前的文章中注意到的,我们构建了一个受保护的资源 (OrdersController),可以通过向端点 (http://ngauthenticationAPI.azurewebsites.net/api/orders) 发出 HTTP GET 来访问,该端点被视为资源,应移至我们现在要构建的资源服务器,但为了保持本系列所有文章的正常运行;我将把这个端点保留在我们的授权服务器中,因此请将我们之前构建的 API 视为我们的独立授权服务器,即使其中包含受保护的资源。
构建资源服务器的步骤
在下面的步骤中,我们将构建另一个包含我们受保护资源的 Web API,为了简单起见,我将只添加一个名为“ProtectedController”的安全端点,任何对此端点的授权请求都应包含由我们的授权服务器颁发的有效承载令牌,所以让我们开始实现它
步骤 1:创建资源 Web API 项目
我们需要将名为“AngularJSAuthentication.ResourceServer”的新 ASP.NET Web 应用程序添加到我们现有的名为“AngularJSAuthentication”的解决方案中,新项目的所选模板将是“空”模板,不带核心依赖项,请查看下图
步骤 2:安装所需的 NuGet 包
这个项目是空的,所以我们需要安装设置 OWIN 资源服务器所需的 NuGet 包,配置 ASP.NET Web API 以托管在 OWIN 服务器中,并将其配置为仅使用 OAuth 2.0 承载令牌作为授权中间件;因此,打开 NuGet 包管理器控制台并安装以下包
Install-Package Microsoft.AspNet.WebApi -Version 5.2.2 Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.2 Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0 Install-Package Microsoft.Owin.Cors -Version 3.0.0 Install-Package Microsoft.Owin.Security.OAuth -Version 2.1.0
重要提示:在最初的文章中,我使用的是“Microsoft.Owin.Security.OAuth”包版本“3.0.0”,这与授权服务器使用的版本“2.1.0”不同,当我在使用两个不同版本时,我的解决方案中存在一个错误,基本上当我们将过期的令牌发送到资源服务器时会发生错误,结果是资源服务器即使令牌已过期也接受了该令牌。我注意到身份验证票证的属性为 null,并且没有过期日期。要解决此问题,我们需要统一授权服务器和资源服务器之间的程序集版本“Microsoft.Owin.Security.OAuth”,我认为这两个版本之间存在重大更改,这就是票证属性未正确解密和反序列化的原因。感谢 Ashish Verma 告知我这一点。
每个包的用法已在之前的文章中介绍过,欢迎查看这篇文章,了解使用每个包的原理。
步骤 3:添加 OWIN“Startup”类
现在我们要添加一个名为“Startup”的新类。它将包含以下代码
<!-- Crayon Syntax Highlighter v2.6.6 -->
[assembly: OwinStartup(typeof(AngularJSAuthentication.ResourceServer.Startup))] namespace AngularJSAuthentication.ResourceServer { public class Startup { public void Configuration(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); ConfigureOAuth(app); WebApiConfig.Register(config); app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); app.UseWebApi(config); } private void ConfigureOAuth(IAppBuilder app) { //Token Consumption app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions { }); } } }
正如我们之前文章中介绍的,一旦我们的资源服务器启动,这个类就会被触发,正如您所注意到的,我已经启用了 CORS,这样这个资源服务器就可以接受来自任何源的 XHR 请求。
这里值得注意的是“ConfigureOAuth”方法的实现,在这个方法内部,我们将资源服务器配置为接受(只消费)具有 Bearer 方案的令牌,如果您忘记了这一点;那么您的资源服务器将不理解发送给它的 Bearer 令牌。
步骤 3:添加“WebApiConfig”类
像往常一样,我们需要在“App_Start”文件夹下添加 WebApiConfig 类,其中包含以下代码
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
这个类负责在我们的控制器上启用路由属性。
步骤 4:添加新的受保护(安全)控制器
现在我们要添加一个控制器,它将作为我们的受保护资源,这个控制器将返回授权用户的声明列表,这些声明当然编码在我们从授权服务器获得的访问令牌中。因此,在“Controllers”文件夹下添加一个名为“ProtectedController”的新控制器,并粘贴以下代码
[Authorize] [RoutePrefix("api/protected")] public class ProtectedController : ApiController { [Route("")] public IEnumerable<object> Get() { var identity = User.Identity as ClaimsIdentity; return identity.Claims.Select(c => new { Type = c.Type, Value = c.Value }); } }
请注意我们如何使用 [Authorize] 属性来修饰控制器,这将保护此资源,并且只允许授权头中包含有效 Bearer 访问令牌的 HTTP GET 请求通过,更详细地说,当收到请求时,OAuth Bearer 身份验证中间件将执行以下操作
- 提取在“Authorization header”中以“Bearer”方案发送的访问令牌。
- 从访问令牌中提取身份验证票证,此票证将包含声明身份和任何额外的身份验证属性。
- 检查身份验证票证的有效期。
步骤 5:将授权服务器和资源服务器托管在不同的机器上
直到这一步,如果您正在本地开发机器上进行开发,如果您尝试从授权服务器(例如 http://AuthServer/token)获取访问令牌,然后将此访问令牌发送到资源服务器中的安全端点(例如 http://ResServer/api/protected),此请求将通过,您将有权访问受保护的资源,这是如何发生的?
在您的开发机器上,一旦您从授权服务器请求访问令牌;OAuth 中间件将使用授权服务器中的默认数据保护提供程序,因此它将使用存储在 machine.config 文件中的 machineKey 节点中的“validationKey”值来颁发访问令牌并保护它。当您将访问令牌发送到资源服务器时,同样适用,它将使用相同的 machineKey 来解密访问令牌并从中提取身份验证票证。
如果您计划在生产环境中将授权服务器和资源服务器托管在同一台机器上,则同样适用。
但在我们的情况下,我将我的授权服务器托管在 Azure 网站的美国西部区域,而我的资源服务器托管在 Azure 网站的美国东部区域,因此两者完全位于不同的机器上,并且它们共享不同的 machine.config 文件。当然,我无法更改 machine.config 文件中的任何内容,因为它被我托管 API 的 Azure VM 上的不同站点使用。
因此,为了解决这种情况,我们需要覆盖两个 API(授权服务器和资源服务器)的 machineKey 节点,并在两个 web.config 文件之间共享相同的 machineKey。
为此,我们需要使用此在线工具生成一个新的 machineKey,使用该工具后的结果如下:正如Barry Dorrans所建议的,不建议使用在线工具生成您的 machineKey,因为您不知道此在线工具是否会将您的 machineKey 值存储在某个秘密数据库中,最好自己生成 machineKey。
为此,我将按照文章 KB 2915218,附录 A 中提到的步骤,该步骤使用 PowerShell 命令生成 machineKey,因此在您打开 PowerShell 并运行正确的命令(如下图所示)后,您将收到新的 machineKey
<machineKey validationKey="A970D0E3C36AA17C43C5DB225C778B3392BAED4D7089C6AAF76E3D4243E64FD797BD17611868E85D2E4E1C8B6F1FB684B0C8DBA0C39E20284B7FCA73E0927B20" decryptionKey="88274072DD5AC1FB6CED8281B34CDC6E79DD7223243A527D46C09CF6CA58DB68" validation="SHA1" decryption="AES" />
注意:请勿将这些用于生产环境,并使用 PowerShell 为您的服务器生成新的 machineKey。
生成后,打开授权服务器(AngularJSAuthentication.API 项目)和资源服务器(AngularJSAuthentication.ResourceServer)的 web.config 文件,并将 machineKey 节点粘贴到 <system.web> 节点内,如下所示
<system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> <machineKey validationKey="VALUE GOES HERE" decryptionKey="VALUE GOES HERE" validation="SHA1" decryption="AES"/> </system.web>
通过这样做,我们统一了授权服务器和资源服务器的 machineKey(在 Azure 共享托管或任何其他共享主机的情况下,仅此 API),现在我们准备测试我们的实现。
步骤 6:测试授权服务器和资源服务器
现在我们需要像之前文章中那样从我们的授权服务器获取访问令牌,所以我们可以向端点 (http://ngauthenticationapi.azurewebsites.net/token) 发出 HTTP POST 请求,包括资源所有者的用户名和密码,如下图所示
如果请求成功,我们将收到一个访问令牌,然后我们将使用此访问令牌向我们的新资源服务器发送 HTTP GET 请求,使用受保护的端点 (http://ngauthenticationresourcesapi.azurewebsites.net/api/protected),当然我们需要在 Authorization 标头中使用 Bearer 方案发送访问令牌,请求将如下图所示
正如您所注意到的,我们现在能够访问我们新的资源服务器中受保护的资源,并且从授权服务器获得的此身份的声明已被提取。
各位,今天的分享就到这里,希望这篇简短的教程能帮助大家理解如何将授权服务器与资源服务器解耦。
如果您有任何评论或问题,请给我留言
您可以使用以下链接查看演示应用程序。
您可以使用以下链接测试授权服务器 (http://ngauthenticationAPI.azurewebsites.net),
您可以使用以下链接测试资源服务器 (http://ngauthenticationResourcesAPI.azurewebsites.net)
您可以在 Github 上查看源代码。