65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Identity Server 和 .NET Core 构建简单的 OAuth2 授权服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (25投票s)

2017 年 4 月 22 日

CPOL

7分钟阅读

viewsIcon

114556

downloadIcon

3426

一篇指导性教程,介绍如何使用 Identity Server 和 .NET Core 构建一个简单的授权服务器,并启用客户端凭据工作流。

引言

本文是一篇简短易懂的教程,将解释如何使用 Identity Server 开源中间件构建一个 OAuth2 授权服务器,并将其托管在 .NET Core Web 服务器中。

Identity Server 项目的作者们已经做了出色的工作,提供了非常棒的文档和许多清晰有用的快速入门示例。然而,有时候很难理解示例的作者是如何做到这一点的。通常,从头开始重建同一个示例有助于更好地理解我们正在学习的技术。

本文来源于这些考量。我的想法是与您分享我学习这个主题的经验,希望能对其他开发者有所帮助。

背景

要理解本文内容,您可能需要了解更多关于

让我们创建授权服务器

在下一节中,我将(几乎)一步一步地解释代码。因为我知道我们都不喜欢阅读太多内容,所以我将每个部分都组织了清晰的段落标题,这样您就可以滚动浏览,找到可能更感兴趣的部分。开始吧!

在 Visual Studio 2017 中创建初始 WebAPI 项目

整个示例目前仅适用于 VS2017,使用 .NET Core 1.1 构建。方法如下:

  • 打开 VS2017 并创建一个新项目,选择 VisualC# -> Web -> ASP.NET Core Web Application (.NET Core)。将其命名为 AuthorizationServer。按“确定”。
  • 现在选择 WebApi 项目类型。选择 ASP.NET Core 1.1。再次按“确定”。项目已创建。

向 WebAPI 服务器添加 NugetPackage IdentityServer4

  • 浏览程序包管理器并安装 IdentityServer4 包。我安装了 1.5.0 版本,这是我撰写本文时最新的稳定版本。

向 WebAPI 服务器添加欢迎页面

为了让您的授权服务器能够通过浏览器访问,并让您轻松理解服务器是否已启动并正在运行,您可以添加一个基本控制器和一个欢迎页面。我将详细介绍我遵循的步骤,以便不熟悉 ASP.NET MVC 框架的人也能理解。

添加 MVC 控制器

  • Controllers 文件夹中,添加一个名为 HomeController 的新控制器。
  • 添加新控制器时,VS 会询问要向项目添加哪些依赖项。目前您可以选择 **Minimal Dependencies**。
  • 在我撰写本文时,添加依赖项后,我需要再次添加控制器。我认为这是 UI 的一个小 bug。
  • 现在,添加控制器时,VS 会询问使用哪个脚手架。选择 **MVC Controller - Empty**。
  • 最后,选择 HomeController 名称。现在已创建一个可以管理 Index.cshtml 页面的基本控制器。

添加 MVC 视图

  • 再次,在 VS2017 中,右键单击项目并选择 **Add new folder**。
  • 将其命名为 Views(**不要更改!这是 ASP.NET MVC 的默认选项!**)。
  • 现在,在此 Views 文件夹中,添加另一个名为 Home 的文件夹。
  • 现在右键单击 Home 文件夹,选择 Add -> New item -> MVC View Page (ASP.NET Core)
  • 默认情况下,视图的名称是 Index.cshtml,这正是我们想要的。按 **Add** 按钮添加新视图。

编写视图的 HTML 内容

Index.cshtml 中的代码更改为以下代码,以创建一个欢迎消息。

<html>
<head>
<title>Authorization server. Welcome page.</title>
</head>
<body>
<h1>Authorization server. Welcome page.</h1> 
</body> 
</html>

配置 Startup 类

Startup.cs 中设置正确的路由,以便能够浏览网站。

public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

现在浏览 Properties -> LaunchSettings.json 文件,并删除文件中所有可见部分的包含 launchUrl 属性的行。

 "profiles": {
  "IIS Express": {
   "commandName": "IISExpress",
   "launchBrowser": true,
   "launchUrl": "api/values", //REMOVE THIS ONE
   "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
   }
  },
  "AuthorizationServer": {
   "commandName": "Project",
   "launchBrowser": true,
   "launchUrl": "api/values", //REMOVE THIS ONE
   "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
   },
   "applicationUrl": "https://:50011"
  }

没有此属性,Visual Studio 将会按照默认路径启动主页,并自动指向我们刚刚创建的网页。

启动项目以检查它是否正常工作...

...并享受第一个成就!**警告**:有些人报告了 VS2017 中与使用 IISExpress 启动 WebAPI 项目相关的一些问题。如果您是其中之一,可以更改“**Start project**”按钮旁边的配置文件,从 **IISExpress** 切换到 **AuthorizationServer**。

为客户端凭据工作流配置 **示例** 范围和客户端

现在我们可以配置授权服务器最重要的元素:clientIdsclientSecretsscopes
这三个元素是客户端凭据工作流的一些基本要素。不要忘记参考 OAuth2 客户端注册文档 以获取更多信息!

这些属性将定义 WHO 可以向您的授权服务器请求访问令牌,以及 WHO 可以使用该令牌做什么。

为了激活我们最初的 **示例** 配置,只需在我们的项目中创建一个 Config.cs 类,内容如下:

using IdentityServer4.Models;
using System.Collections.Generic;

namespace AuthorizationServer
{
  public class Config
  {
    // scopes define the API resources in your system
    public static IEnumerable<ApiResource> GetApiResources()
    {
      return new List<ApiResource>
      {
        new ApiResource("scope.readaccess", "Example API"),
        new ApiResource("scope.fullaccess", "Example API"),
        new ApiResource("YouCanActuallyDefineTheScopesAsYouLike", "Example API")
      };
    }

    // client wants to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
      return new List<Client>
      {
    new Client
          {
            ClientId = "ClientIdThatCanOnlyRead",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret1".Sha256())
            },
            AllowedScopes = { "scope.readaccess" }
          },
    new Client
          {
            ClientId = "ClientIdWithFullAccess",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret2".Sha256())
            },
            AllowedScopes = { "scope.fullaccess" }
          }
      };
    }
  }
}

花点时间理解这个类...

花点时间理解这个类中的配置工作原理。这是我们示例的关键部分。

我们首先在这里定义了三个范围

return new List<ApiResource>
      {
        new ApiResource("scope.readaccess", "Example API"),
        new ApiResource("scope.fullaccess", "Example API"),
        new ApiResource("YouCanActuallyDefineTheScopesAsYouLike", "Example API")
      };

正如您所见,我们可以使用任何我们想要的 string 作为范围。它只是一个标识符,仅此而已。

当被请求时,AuthorizationServer 将向客户端颁发一个 JWT Token,并且基于 clientId,将正确的范围包含在 Token 中。由于范围被加密在 Token 中,因此接收 Token 的客户端无法更改范围并为自己启用比我们想要的更多的权限,这没有风险。

实际使用此范围的逻辑将在我们稍后创建的 Web API 服务器中(我计划很快在另一个示例/文章中这样做),该服务器将使用此授权服务器进行保护。Web API 服务器在执行实际操作之前,会检查从客户端传入的范围是否包含正确的授权。这就是我们利用授权服务器的地方。

哪个 ClientId 可以请求 Token,以及它获得什么范围?这就是配置类的第二部分所定义的。

// client want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
      return new List<Client>
      {
    new Client
          {
            ClientId = "ClientIdThatCanOnlyRead",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret1".Sha256())
            },
            AllowedScopes = { "scope.readaccess" }
          },
    new Client
          {
            ClientId = "ClientIdWithFullAccess",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret2".Sha256())
            },
            AllowedScopes = { "scope.fullaccess" }
          }
      };
    }

启用 IdentityServer 中间件功能

现在是时候启用 IdentityServer 功能,并将我们空网站的转换完成为一个真正的授权服务器,使其能够管理和认证我们在上面 Config 类中配置的客户端。

为此,我们只需在 Startup 类中调用几次 IdentityServer 对象。

将以下方法复制并粘贴到 Startup 类中,替换旧的方法。

// This method gets called by the runtime. Use this method to add services to the container.     
public void ConfigureServices(IServiceCollection services)
    {
      // configure identity server with in-memory stores, keys, clients and scopes
      services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());

      //add framework services
      services.AddMvc();
    }

该方法“启用”了 IdentityServer 中间件,并为我们的范围和客户端添加了 InMemory 管理。

这是我们现在使用之前创建的 Config 类的关键点。

        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());

identityserver 上还有很多,**非常多** 的其他选项可以配置,但这超出了本文的范围。在这里,我们只是创建一个快速入门。再次,请查看文档,那里的人们确实开发了一个惊人的开源库。

现在,在 Startup.Configure 方法中,添加以下行:

           app.UseIdentityServer(); 

最后,该方法应该如下所示:

 // This method gets called by the runtime. Use this method to configure the 
 // HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
                          ILoggerFactory loggerFactory)
    {
      loggerFactory.AddConsole(Configuration.GetSection("Logging"));
      loggerFactory.AddDebug();

      app.UseIdentityServer(); //HERE WE ENABLE IDENTITY SERVER

      app.UseMvc(routes =>
      {
        routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
      });
    }

检查一切是否正常

我们几乎完成了。您的授权服务器已准备就绪!

因此,再次从 VS2017 启动项目。欢迎页面会显示出来,让您确信 Web 服务已启动并正在运行。
现在转到以下地址(**请将端口号替换为您服务器的端口号**):

  • https://:50151/.well-known/openid-configuration

如果一切正常,JSON 文件将加载并在浏览器中显示(或者会提示您下载它,具体取决于您的浏览器)。这是来自 Identity Server 中间件生成的所有配置信息的 JSON 文件。

仔细查看此 JSON 文件。其中有一个重要部分,显示中间件已正确理解了您的配置。这是声明您的授权服务器支持的范围的部分,正是您在 Config.cs 中声明的那些范围。

 "scopes_supported": [
    "scope.readaccess",
    "scope.fullaccess",
    "YouCanActuallyDefineTheScopesAsYouLike",
    "offline_access"
  ],

就这样!……嗯,暂时是这样……

您的第一个授权服务器已准备就绪,但如何使用呢?单独一个授权服务器本身作用不大,如果没有 API 来保护,或者没有客户端来授权。它需要一些朋友!

因此,在另一篇文章中,我们将学习如何保护 Web API 服务器,接受来自此授权服务器颁发的 Token。我们还将学习如何创建一个可以请求 Token 并使用它的客户端。

与此同时,我希望这个示例能帮助那些像我一样想开始玩转 OAuth2 工作流和强大的 IdentityServer 中间件的开发者。请分享您的反馈和评论!

历史

  • 2017-04-22:初版
  • 2017-04-24:添加了下载源代码的链接
  • 2017-05-07:修复了一个提到错误按钮名称的步骤
© . All rights reserved.