Azure API管理中高效的OAuth授权管理

在开发一个应用程序时,大多数时候,你使用API调用来发送和接收信息。因此,你应该遵循一个认证和授权过程来使用API,除非它是公共API。这些认证/授权过程使用1)一个认证密钥或2)一个通过OAuth过程的访问令牌。

使用auth密钥的方法,你可以把它存储在一个安全的地方,比如Azure密钥库服务,然后获取密钥。这是一个相对简单的过程。但是,如果你需要使用OAuth流程,就会因为下面的授权流程而变得复杂。下面是概念性的OAuth流程。

  1. 请求一个授权码
  2. 收到授权码
  3. 通过提供授权码来请求一个访问令牌
  4. 收到访问令牌和刷新令牌
  5. 通过提供访问令牌来调用API请求
  6. 接收API响应
  7. 一旦现有的访问令牌过期,通过提供刷新令牌请求新的访问令牌
  8. 收到新的访问令牌和新的刷新令牌
  9. 通过提供新的访问令牌来调用API请求
  10. 接收API响应

Conceptual OAuth Flow

如果你想了解更多关于OAuth auth的流程,请参考本文档

因此,开发人员应该将这些流程作为应用开发的一部分来实现。如果你想使用的服务提供一个SDK,你可能会很幸运。如果没有,你应该自己做,这非常麻烦。如果有人为你做所有繁琐的步骤呢?比方说,在安全的地方,有人代表你做所有的认证过程,并简单地返回访问令牌。如果这种情况发生,你的应用开发速度将大大增加。Azure API管理(APIM)最近发布了一个名为 "Authorisations "的预览功能,可以代表你完成OAuth流程。在这篇文章中,我将使用一个托管在Azure静态Web应用(SWA)上的Blazor Web Assembly(WASM)应用来讨论这个功能。

你可以从这个GitHub资源库中下载示例应用代码。

仓库中有两个应用,一个是基于Blazor WASM的,另一个是基于React的。我将在这里使用Blazor WASM的样本。如果你对React应用的样本感兴趣,请访问我的同事Aaron Powell博文

Blazor网络装配应用

关于Blazor WASM的总体情况,请参考Blazor教程文件。相反,我打算看一下负责OAuth授权和访问令牌的组件。这个组件接收用户的输入,并将其存储为.csv文件格式,然后上传到DropBox。下面的Razor代码没有什么特别之处,但包含一个用户输入的表单。当用户完成表格并点击**"提交**"按钮时,OnFormSubmittedAsync 事件被触发(第3行)。

<div class="container-sm" style="max-width: 540px;">
  <h1>Blazor Lead Capture</h1>
  <form class="clearfix" @onsubmit="OnFormSubmittedAsync">
    <fieldset>
      <div>
        <label for="firstName" class="form-label">First name</label>
        <input type="text" class="form-control" id="firstName" name="firstName" placeholder="Justin" value="@userInfo.FirstName" @onchange="@(e => OnFieldChanged(e, "firstName"))" />
      </div>
      <div>
        <label for="lastName" class="form-label">Last name</label>
        <input type="text" class="form-control" id="lastName" name="lastName" placeholder="Yoo" value="@userInfo.LastName" @onchange="@(e => OnFieldChanged(e, "lastName"))" />
      </div>
    </fieldset>

    <fieldset>
      <div>
        <label htmlFor="email" class="form-label">Email</label>
        <input type="email" class="form-control" id="email" name="email" placeholder="[email protected]" value="@userInfo.Email" @onchange="@(e => OnFieldChanged(e, "email"))" />
      </div>
      <div>
        <label htmlFor="phone" class="form-label">Phone</label>
        <input type="phone" class="form-control" id="phone" name="phone" placeholder="555-555-555" value="@userInfo.Phone" @onchange="@(e => OnFieldChanged(e, "phone"))" />
      </div>
    </fieldset>

    <fieldset>
      <button type="submit" class="btn [email protected]" disabled="@(componentUIInfo.Submitting || string.IsNullOrWhiteSpace(userInfo.FirstName) || string.IsNullOrWhiteSpace(userInfo.LastName) || string.IsNullOrWhiteSpace(userInfo.Email) || string.IsNullOrWhiteSpace(userInfo.Phone))">
        <span>Submit</span>
        <span class="spinner-border spinner-border-sm" style="display:@componentUIInfo.DisplaySpinner;" role="status" aria-hidden="true"></span>
      </button>
    </fieldset>
  </form>

  <div class="alert [email protected]" style="display:@componentUIInfo.DisplayResult;">
    <h2>@componentUIInfo.MessageResult</h2>
    <button type="reset" class="btn btn-dark" @onclick="ResetFields">
      <span>Start Over?</span>
    </button>
  </div>
</div>
复制代码

下面的@code { ... } 块是与Razor组件交互的C#代码。为了简洁起见,我留下了两个方法。第一个方法是事件处理程序,OnFormSubmittedAsync ,由**"提交**"按钮触发。然后,在事件处理程序中,它调用了SaveToDropboxAsync 方法,它负责所有的事情,包括获取访问令牌和DropBox保存。

@code {
    ...
    protected async Task OnFormSubmittedAsync(EventArgs e)
    {
        ...
        await SaveToDropboxAsync().ConfigureAwait(false);
    }
复制代码

SaveToDropboxAsync 方法中,它首先获得APIM_Endpoint 的环境变量(第4行)。Blazor WASM将所有的环境变量存储在appsettings.json 文件中,我将在后面讨论这个问题。通过从appsettings.json 中调用APIM端点,应用程序获得了DropBox的访问令牌(第7行),而DropBox客户端实例则上传了数据。

    private async Task SaveToDropboxAsync()
    {
        // Gets the APIM endpoint from appsettings.json
        var requestUrl = Configuration.GetValue<string>("APIM_Endpoint");

        // Gets the auth token from APIM
        var token = await Http.GetStringAsync(requestUrl).ConfigureAwait(false);

        // Builds contents.
        var path = $"/submissions/{DateTimeOffset.UtcNow.ToString("yyyyMMddHHmmss")}.csv";
        var contents = $"{userInfo.FirstName},{userInfo.LastName},{userInfo.Email},{userInfo.Phone}";
        var bytes = UTF8Encoding.UTF8.GetBytes(contents);

        // Uploads the contents.
        var result = default(FileMetadata);
        using(var dropbox = new DropboxClient(token))
        using(var stream = new MemoryStream(bytes))
        {
            result = await dropbox.Files.UploadAsync(path, WriteMode.Overwrite.Instance, body: stream).ConfigureAwait(false);
        }

        ...
    }
}
复制代码

让我们看一下包含APIM_Endpoint 环境变量的appsettings.json 文件。该值是用于获取DropBox访问令牌的APIM端点。

{
  "APIM_Endpoint": "https://<APIM_NAME>.azure-api.net/dropbox-demo/token?subscription-key=<APIM_SUBSCRIPTION_KEY>"
}
复制代码

在你的本地机器上运行Blazor WASM应用程序。

dotnet watch run
复制代码

然后,打开你的网络浏览器,输入https://localhost:5001 ,你会看到这样的页面。

Blazor WASM Landing Page

填写表格并点击**"提交**"按钮,应用程序将把表格的详细信息保存到DropBox。然后,如果你打开你的网络浏览器的开发工具,你可以看到Blazor WASM应用程序如何调用APIM端点。

Request Access Token #1

而API调用将访问令牌返回给DropBox。

Request Access Token #2

有了这个访问令牌,您就可以创建和存储一个文件。这就是结果。

Dropbox Upload Result

你可以回忆一下,DropBox没有OAuth授权的代码。相反,它从直接获得访问令牌的API调用开始。那么,代码如何在获得访问令牌之前删除所有的预检代码呢?这就是这篇文章中提到的新的APIM授权功能。简而言之,APIM实例在内部代表Blazor WASM应用执行所有OAuth相关的流程,并简单地返回访问令牌。

Azure API管理实例

让我们来探讨一下新的APIM功能。你可以点击下面的按钮,一次性将所有资源配置到Azure上。

Deploy To Azure

另外,你也可以运行下面声明的bicep文件。让我们进一步了解一下bicep文件。首先,声明APIM实例。你可能会注意到,它启用了管理身份功能(第13-15行),我将在本篇文章后面讨论。为了方便起见,将APIM实例命名为token-store-demo-apim ,并将位置设置为West Central US 。该规定的资源组被设置为rg-token-store-demo

// APIM instance
resource apim 'Microsoft.ApiManagement/service@2021-08-01' = {
  name: 'token-store-demo-apim'
  location: 'westcentralus'
  sku: {
    name: 'Developer'
    capacity: 1
  }
  properties: {
    publisherName: 'John Doe'
    publisherEmail: '[email protected]'
  }
  identity: {
    type: 'SystemAssigned'
  }
}
复制代码

下一步是CORS策略,因为Azure SWA直接调用APIM端点。在inbound 节点内,添加cors 节点,在其下添加allowed-origins 节点,并添加origin 节点,其值为* 。确保这只是为了演示目的。你应该添加特定的URL,以便在上线后有更好的安全性。

// Service Policy
resource apim_policy 'Microsoft.ApiManagement/service/policies@2021-08-01' = {
  parent: apim
  name: 'policy'
  properties: {
    value: service_policy
    format: 'xml'
  }
}

// Service Policy Definition
var service_policy = '''
<policies>
    <inbound>
        <cors allow-credentials="false">
            <allowed-origins>
                <origin>*</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
            </allowed-methods>
        </cors>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound />
    <on-error />
</policies>'''
复制代码

在声明了APIM实例之后,定义一个API和它的操作。API的serviceUrl 被设置为DropBox API的基本URL,操作端点被设置为/token ,返回访问令牌。因此,整个APIM端点可能看起来像https://token-store-demo-apim.azure-api.net/dropbox-demo/token

// API
resource api 'Microsoft.ApiManagement/service/apis@2021-08-01' = {
  name: 'dropbox-demo'
  parent: apim
  properties: {
    serviceUrl:'https://api.dropboxapi.com'
    path: 'dropbox-demo'
    displayName:'dropbox-demo'
    protocols:[
      'https'
    ]
  }
}

// Operation
resource api_gettoken 'Microsoft.ApiManagement/service/apis/operations@2021-08-01' = {
  name: 'gettoken'
  parent: api
  properties: {
    method: 'GET'
    urlTemplate: '/token'
    displayName: 'gettoken'
  }
}
复制代码

然而,这个端点在DropBox API上并不存在。因此,添加操作策略以使该操作工作。

// Operation Policy
resource api_gettoken_policy 'Microsoft.ApiManagement/service/apis/operations/policies@2021-08-01' = {
  parent: api_gettoken
  name: 'policy'
  properties: {
    value: operation_token_policy
    format: 'xml'
  }
}
复制代码

下面的操作策略的XML文档解释了这个APIM的新功能的核心思想。在inbound 下添加get-authorization-context 节点。它有以下属性。

  • provider-id:dropbox-demo
  • authorization-id:auth
  • context-variable-name:auth-context
  • identity-type:managed

正如你所看到的,APIM实例已经启用了与identity-type 对应的管理身份功能。provider-idauthorisation-id 都将在后面使用。context-variable-name 属性被设置为auth-context 。它被用在持有访问令牌值的return-response 节点中。总的来说,这个操作策略代表SWA应用程序来获取访问令牌。

// Operation Token Policy Definition
var operation_token_policy = '''
<policies>
    <inbound>
        <base />
        <get-authorization-context provider-id="dropbox-demo" authorization-id="auth" context-variable-name="auth-context" ignore-error="false" identity-type="managed" />
        <return-response>
            <set-body>@(((Authorization)context.Variables.GetValueOrDefault(&quot;auth-context&quot;))?.AccessToken)</set-body>
        </return-response>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>'''
复制代码

你已经定义了APIM实例。现在,你需要为Blazor WASM应用提供SWA应用实例。

Azure静态Web应用实例

为了托管Blazor WASM应用,你需要配置一个Azure SWA实例。下面是它的二进制代码。首先,给应用程序命名为token-store-demo-blazor-swa ,并在rg-token-store-demo 的资源组下设置Central US 的位置。

// SWA instance
resource sttapp 'Microsoft.Web/staticSites@2021-02-01' = {
  name: 'token-store-demo-blazor-swa'
  location: 'centralus'
  sku: {
    name: 'Free'
  }
  properties: {
    allowConfigFileUpdates: true
    stagingEnvironmentPolicy: 'Enabled'
  }
}
复制代码

Blazor WASM应用部署到Azure静态Web应用实例上

你已经有了新的ASWA实例。现在是部署Blazor WASM应用程序的时候了,它位于src/frontend/blazor 目录中。第一步应该是添加appsettings.json ,该文件包含访问令牌的APIM端点。下面的命令是如何获得APIM的端点URL。

# Get APIM gateway URL
rg_name=rg-token-store-demo
apim_name=token-store-demo-apim

gateway_url=$(az apim show -g $rg_name -n $apim_name --query "gatewayUrl" -o tsv)

# Get APIM subscription key
subscription_id=$(az account show --query "id" -o tsv)
apim_secret_uri=/subscriptions/$subscription_id/resourceGroups/$rg_name/providers/Microsoft.ApiManagement/service/$apim_name/subscriptions/master/listSecrets
api_version=2021-08-01

subscription_key=$(az rest --method post --uri $apim_secret_uri\?api-version=$api_version | jq '.primaryKey' -r)

# Build APIM endpoint
apim_endpoint=$gateway_url/dropbox-demo/token\?subscription-key=$subscription_key
复制代码

APIM的端点URL最后落在了apim_endpoint 这个变量中。因此,将src/frontend/blazor/wwwroot 目录下的appsettings.sample.json 文件重命名为appsettings.json ,并更新端点。

{
  "APIM_Endpoint": "<apim_endpoint>"
}
复制代码

构建并创建Blazor WASM应用程序的构件。

dotnet restore ./src/frontend/blazor
dotnet build ./src/frontend/blazor
dotnet publish ./src/frontend/blazor -c Release -o ./src/frontend/blazor/bin
复制代码

还有一个部署的步骤。通过运行下面的命令获得部署密钥。

swa_key=$(az staticwebapp secrets list \
    -g rg-token-store-demo \
    -n token-store-demo-blazor-swa \
    --query "properties.apiKey" -o tsv)
复制代码

最后,通过运行该命令来部署Blazor WASM应用程序。

swa deploy -a ./src/frontend/blazor/bin/wwwroot -d $swa_key --env default
复制代码

虽然到目前为止一切都很顺利,但在提交表格后,你可以看到下面的错误。

Internal Server Error

这是因为你还没有对DropBox应用程序进行同意。最后一步将是同意。

DropBox应用程序的同意

按照此文件创建DropBox应用程序,你会得到App keyApp secret

Dropbox App

你需要这两个值在APIM实例内进行同意。点击刀片处的**"授权(预览)**"。

Authorizations Preview

目前还没有授权应用。因此,点击**"创建**"按钮。

Authorizations Preview Pane

你可以回忆一下你是如何为操作策略设置get-authorisation-context 结点的。现在是使用它们的时候了。

  • 提供商名称栏中输入dropbox-demo
  • 身份提供者字段中选择DropBox
  • 客户ID字段中输入DropBox的应用程序密钥值。
  • 在 "客户秘密"字段中输入DropBox应用程序的安全值。
  • 在 "范围"字段中输入files.metadata.write files.content.write files.content.read
  • 在"授权名称"字段中输入auth

Create Authorization

点击"创建"按钮,获得重定向URL。

Redirection URL

将重定向URL添加到DropBox应用程序中。

DropBox App Update

回到APIM实例,登录到DropBox应用程序。然后,连续点击**"继续**"、"允许"和**"允许访问"按钮,进行弹出的窗口。

DropBox App Login on APIM

然后,你会看到成功信息。

DropBox App Authorized

现在DropBox应用程序已经被授权。但是APIM实例还没有被授权访问DropBox应用程序。由于APIM实例已经启用了身份管理功能,让我们来使用它。选择"管理身份"并点击"添加成员"按钮。

APIM Managed Identity

找到APIM实例并添加它。

Add APIM Managed Identity

现在,APIM和DropBox都可以互相通信了。

APIM Managed Identity Added

你能看到DropBox应用程序被授权吗?

DropBox Authorization Created

让我们测试一下端点是否真的能工作。按照菜单"APIs"➡️"Dropbox-demo"➡️"gettoken",点击"Send"按钮。

APIM Test

然后你会看到访问令牌成功发出。

APIM Test Success

让我们回到ASWA应用中,填写表格。这次没有错误了。

Static Web App to APIM Request Success

而且上传的文件出现在DropBox上!

DropBox File Created

到目前为止,我们已经讨论了Azure API管理的新OAuth授权功能。最终,我们需要通过OAuth过程获得访问令牌。APIM执行这个过程。因此,我们可以不在我们的应用中实现这一功能,从而节省大量的时间。由于它目前处于预览阶段,一些功能可能已经准备好了,但它们会被不断改进。

如果你想了解更多关于这个APIM OAuth授权管理功能,请访问下面的文档。

猜你喜欢

转载自juejin.im/post/7107112596926365710