在开发一个应用程序时,大多数时候,你使用API调用来发送和接收信息。因此,你应该遵循一个认证和授权过程来使用API,除非它是公共API。这些认证/授权过程使用1)一个认证密钥或2)一个通过OAuth过程的访问令牌。
使用auth密钥的方法,你可以把它存储在一个安全的地方,比如Azure密钥库服务,然后获取密钥。这是一个相对简单的过程。但是,如果你需要使用OAuth流程,就会因为下面的授权流程而变得复杂。下面是概念性的OAuth流程。
- 请求一个授权码
- 收到授权码
- 通过提供授权码来请求一个访问令牌
- 收到访问令牌和刷新令牌
- 通过提供访问令牌来调用API请求
- 接收API响应
- 一旦现有的访问令牌过期,通过提供刷新令牌请求新的访问令牌
- 收到新的访问令牌和新的刷新令牌
- 通过提供新的访问令牌来调用API请求
- 接收API响应
如果你想了解更多关于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
,你会看到这样的页面。
填写表格并点击**"提交**"按钮,应用程序将把表格的详细信息保存到DropBox。然后,如果你打开你的网络浏览器的开发工具,你可以看到Blazor WASM应用程序如何调用APIM端点。
而API调用将访问令牌返回给DropBox。
有了这个访问令牌,您就可以创建和存储一个文件。这就是结果。
你可以回忆一下,DropBox没有OAuth授权的代码。相反,它从直接获得访问令牌的API调用开始。那么,代码如何在获得访问令牌之前删除所有的预检代码呢?这就是这篇文章中提到的新的APIM授权功能。简而言之,APIM实例在内部代表Blazor WASM应用执行所有OAuth相关的流程,并简单地返回访问令牌。
Azure API管理实例
让我们来探讨一下新的APIM功能。你可以点击下面的按钮,一次性将所有资源配置到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-id
和authorisation-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("auth-context"))?.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
复制代码
虽然到目前为止一切都很顺利,但在提交表格后,你可以看到下面的错误。
这是因为你还没有对DropBox应用程序进行同意。最后一步将是同意。
DropBox应用程序的同意
按照此文件创建DropBox应用程序,你会得到App key
和App secret
。
你需要这两个值在APIM实例内进行同意。点击刀片处的**"授权(预览)**"。
目前还没有授权应用。因此,点击**"创建**"按钮。
你可以回忆一下你是如何为操作策略设置get-authorisation-context
结点的。现在是使用它们的时候了。
- 在提供商名称栏中输入
dropbox-demo
。 - 在身份提供者字段中选择
DropBox
。 - 在客户ID字段中输入DropBox的应用程序密钥值。
- 在 "客户秘密"字段中输入DropBox应用程序的安全值。
- 在 "范围"字段中输入
files.metadata.write files.content.write files.content.read
。 - 在"授权名称"字段中输入
auth
。
点击"创建"按钮,获得重定向URL。
将重定向URL添加到DropBox应用程序中。
回到APIM实例,登录到DropBox应用程序。然后,连续点击**"继续**"、"允许"和**"允许访问"按钮,进行弹出的窗口。
然后,你会看到成功信息。
现在DropBox应用程序已经被授权。但是APIM实例还没有被授权访问DropBox应用程序。由于APIM实例已经启用了身份管理功能,让我们来使用它。选择"管理身份"并点击"添加成员"按钮。
找到APIM实例并添加它。
现在,APIM和DropBox都可以互相通信了。
你能看到DropBox应用程序被授权吗?
让我们测试一下端点是否真的能工作。按照菜单"APIs"➡️"Dropbox-demo"➡️"gettoken",点击"Send"按钮。
然后你会看到访问令牌成功发出。
让我们回到ASWA应用中,填写表格。这次没有错误了。
而且上传的文件出现在DropBox上!
到目前为止,我们已经讨论了Azure API管理的新OAuth授权功能。最终,我们需要通过OAuth过程获得访问令牌。APIM执行这个过程。因此,我们可以不在我们的应用中实现这一功能,从而节省大量的时间。由于它目前处于预览阶段,一些功能可能已经准备好了,但它们会被不断改进。
如果你想了解更多关于这个APIM OAuth授权管理功能,请访问下面的文档。