在这篇文章中,我们将看看最近预览的 Azure API管理(APIM)的授权功能,看看如何设置一个React和TypeScript应用程序,使用Dropbox SDK上传文件,而不需要处理OAuth令牌的创建。
什么是APIM授权
在我们深入创建应用程序之前,让我们快速了解一下这个功能是什么。
在一个连接的系统中,能够在不同的软件即服务(SaaS)平台之间进行通信是一项常见的任务,但通常这些平台会使用OAuth2来验证用户身份。这需要进行认证流程,如果你直接使用系统,这很好,但如果是由后台工作处理,比如用定时器触发器运行的Azure函数,那怎么办?那么我们就需要使用其他的认证工作流程,处理令牌的过期,等等。
这可能会导致我们的很多应用代码负责管理和存储令牌。
而这正是Authorizations的用武之地,它是一个管理的令牌库,用于管理你的OAuth2访问令牌。与其说你的应用程序必须进行认证,不如说APIM将代表你处理这个问题。这也意味着你的应用程序可以在一个较低的信任环境中运行,而不是你的应用程序需要知道SaaS提供商的client id/client secret ,它变得不知道,只依赖REST API到API管理,以获得所需的令牌回来。
你可以在APIM的文档中了解更多关于授权的信息。
创建我们的应用程序
我们要创建的应用程序是一个数据输入表单,可以用来在活动中捕获用户信息,一个人输入他们的信息,就会生成一个文件上传到Dropbox,随后可以被我们系统的另一部分摄取。
让我们先用以下方法生成新的应用程序 vite:
npm create vite@latest my-app -- --template react-ts
接下来,我们将开始创建用于数据采集的表单,所以在VS Code(或你选择的任何其他编辑器)中打开my-app 文件夹,我们将在App.tsx 文件中添加一个表单。
const updateField =
(updater: React.Dispatch<React.SetStateAction<UserInfo>>) =>
(e: ChangeEvent<HTMLInputElement>) =>
updater((userInfo) => ({
...userInfo,
[e.target.name]: e.target.value,
}));
function App() {
const [userInfo, setUserInfo] = useState<UserInfo>({});
const [submitting, setSubmitting] = useState(false);
return (
<div className="App">
<header className="App-header">
<h1>Contoso Lead Capture</h1>
<form
action=""
onSubmit={(e) => (e.preventDefault(), setSubmitting(true))}
>
<fieldset>
<div>
<label htmlFor="firstName">First name</label>
<input
type="text"
name="firstName"
id="firstName"
placeholder="Aaron"
value={userInfo.firstName}
onChange={updateField(setUserInfo)}
/>
</div>
<div>
<label htmlFor="lastName">Last name</label>
<input
type="text"
name="lastName"
id="lastName"
placeholder="Powell"
value={userInfo.lastName}
onChange={updateField(setUserInfo)}
/>
</div>
</fieldset>
<fieldset>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="foo@email.com"
value={userInfo.email}
onChange={updateField(setUserInfo)}
/>
</div>
<div>
<label htmlFor="phone">Phone</label>
<input
type="phone"
id="phone"
name="phone"
placeholder="555-555-555"
value={userInfo.phone}
onChange={updateField(setUserInfo)}
/>
</div>
</fieldset>
<fieldset>
<button
type="submit"
disabled={
submitting ||
!userInfo.firstName ||
!userInfo.lastName ||
!userInfo.email ||
!userInfo.phone
}
>
Submit
</button>
</fieldset>
</form>
</header>
</div>
);
}
我还带来了useState 钩子,这样我们就可以在进行过程中设置各个字段的值,并创建一个类型来代表表单中的数据(并把它放在一个名为types.ts 的新文件中)。
export type UserInfo = {
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
};
与Dropbox挂钩
现在是时候与Dropbox挂钩了,所以我们需要他们的JavaScript SDK。
npm install --save dropbox
我们将把保存过程放在一个useEffect 钩子里。
const [dropboxResponse, setDropboxResponse] = useState<
DropboxSaveResponse | undefined
>();
useEffect(() => {
async function saveToDropbox() {
const accessToken = "???";
const dropbox = new Dropbox({ accessToken });
const contents = `${userInfo.firstName},${userInfo.lastName},${userInfo.email},${userInfo.phone}`;
const path = `/submissions/${+new Date()}.csv`;
const response = await dropbox.filesUpload({
path,
contents,
});
if (response.status !== 200) {
setDropboxResponse({
error: true,
message: "Failed to upload to dropbox",
});
return;
}
setDropboxResponse({
error: false,
message: "Details have been saved. Start again?",
});
}
if (!submitting) {
return;
}
saveToDropbox();
}, [submitting, userInfo])
我还创建了一个名为DropboxSaveResponse 的类型,以便在钩子上设置。
export type DropboxSaveResponse = {
error: boolean;
message: string;
};
我们的代码已经准备好了,好吧,除了一个关键部分--我们如何获得Dropbox SDK的访问令牌?好吧,我们可以启动Dropbox认证流程,但现在每个人都必须能够批准对共享Dropbox账户的访问,这并不理想。值得庆幸的是,这正是APIM授权的目的所在。
用授权设置APIM
我们将使用Azure Portal来部署我们的APIM实例,但作为样本 repo的一部分,我们也提供了一些Bicep模板,所以如果这是你喜欢的方法,请到GitHub repo中查看该指南。另外,如果你只是想进行部署,请点击下面的部署到Azure按钮。
注意:请注意这是预览版,所以在最终发布前可能会有一些变化。
前往Azure门户,创建一个新的APIM实例。
填写必要的字段,然后点击其他屏幕(除了第一个屏幕之外,我们不需要再向APIM资源添加任何东西--除非你想为其他用途配置APIM)。
注意:对于预览,你需要使用开发者定价层。
当资源被创建后,你应该在API分组下看到一个新的**授权(预览)**选项。
点击它,我们将看到一个先前创建的授权列表,但由于我们还没有任何授权,我们将从创建按钮开始进行配置。
在这个屏幕上,我们可以配置我们要授权的OAuth2服务,你会在身份提供者列表中看到所有可用的。由于我们使用的是Dropbox,你需要创建一个Dropbox应用程序,并且已经获得了客户ID和客户秘密(如果你还没有这样做,请前往Dropbox并进行设置)。
在填写此表格时,请记下提供者名称和授权名称,因为我们以后会需要这些。
此外,确保你提供的范围与Dropbox中的范围一致。由于我们要上传文件,我们将需要files.metadata.write files.contents.write files.content.read ,但要根据你的应用需求来匹配这些。

在进入下一屏幕之前,复制重定向URL并将其添加到Dropbox应用程序中,这样它就可以在下一步进行验证。
在这个过程的第二步,我们需要使用我们创建的OAuth2应用程序对我们的Dropbox应用程序进行APIM认证,所以点击Login with DropBox按钮并遵循它提供的授权工作流程。
设置授权的最后一个阶段是配置授权将使用的访问策略,你可以将其与AAD中的用户/群组联系起来,也可以使用管理身份,如APIM提供的身份。我们将使用管理身份。
在飞入式窗口中选择API管理服务作为管理身份,然后从列出的选项中选择你的服务。
这将弹出主窗口,我们可以完成设置。
访问我们的令牌
APIM现在作为我们的令牌商店,将根据需要为我们获得新的OAuth2令牌,但我们仍然需要访问它们,为此,我们将在API中创建一个API端点来返回它。前往API部分,我们将手动定义一个HTTP API。
我定义的API将在/token ,由于我们将从另一个虚拟主机调用它,我们需要配置一个CORS策略。我们可以通过点击 "所有操作"并打开代码编辑器来替换默认的策略来做到这一点。
<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>
这是定义一个入站策略,允许来自所有来源的CORS(你可能想在生产应用中收紧这一点!),并通过所有请求到后端而不受干扰。
现在我们可以创建一个到API的操作,这样我们就可以拿回令牌。
我把这个操作称为Get Dropbox token ,并在/ URL上做一个HTTPGET ,这是相对于我们定义的API的路径而言的,这意味着这是一个针对/token 的GET请求。
保存好这些后,我们需要定义这个API将做什么。由于我们想访问我们的授权所使用的令牌存储,我们将在入站请求中使用get-authorization-context 策略。
<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>
get-authorization-context 策略需要我们在最初创建授权时设置的两个信息,即提供者的名称,dropbox-demo ,以及授权的名称,auth 。然后,策略将调用我们的令牌存储,抓取令牌,我们使用set-body ,将其设置为主体,在我们的响应中返回。这只是设置一个text/plain 响应,但如果你的情况更喜欢,你可以建立一个JSON有效载荷。
保存策略,点击顶部的测试标签,然后启动请求。
成功了!我们可以在HTTP响应中看到,响应体包含我们的OAuth2令牌,我们可以向Dropbox SDK提供。
将其全部连接起来
APIM现在已经配置了所有的授权,所以现在是时候与我们的应用程序集成了。
在React应用程序中,我们将调用我们创建的/token API,你可以从这个命令中获得URL。
SUBSCRIPTION_KEY=$(az rest --method post --url /$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ApiManagement/service/$APIM_NAME/subscriptions/master/listSecrets?api-version=2021-08-01 | jq .primaryKey -r)
GATEWAY_URL=$(az apim show --name $APIM_NAME --resource-group $RESOURCE_GROUP --query gatewayUrl --output tsv)
echo "$GATEWAY_URL?dropbox-demo/token?subscription-key=$SUBSCRIPTION_KEY"
注意:我们将在URL中包含subscription key ,该密钥将通过React应用程序暴露出来,因此它可以调用APIM,这意味着你有可能泄露机密。在一个更强大的应用程序中,你可能会包括一个Azure函数,该函数会调用Dropbox,而不是在浏览器中调用,所以你的客户端会POST到Azure函数,它反过来会检索访问令牌并上传文件。但在今天的演示中,我们要把它保留在客户端。
要在我们的React应用程序中使用这个,请在工作区的根部创建一个.env 文件,然后像这样添加进去。
VITE_APIM_ENDPOINT=<...>
现在我们可以回到我们的App.tsx ,更新这一行。
const accessToken = "???";
为了。
const accessTokenResponse = await fetch(import.meta.env.VITE_APIM_ENDPOINT);
const accessToken = await accessTokenResponse.text();
用npm run dev 启动应用程序,在表格中填写数据并点击提交--你会看到对APIM的调用,获得访问令牌,然后它被提供给Dropbox SDK,将文件上传到Dropbox。
总结
你已经了解了我们为API管理添加的新功能--授权。
在这篇文章中,我们已经了解了如何在APIM中设置授权,在这种情况下,我们使用Dropbox,将APIM连接到我们的Dropbox应用程序,它可以代表我们请求OAuth2访问令牌。然后我们在APIM中创建了一个策略,它将通过我们可以进行的API调用返回访问令牌,而不是我们必须从头开始建立自己的API。
我们还建立了一个React应用程序,它可以调用我们在APIM中创建的API,从令牌存储中取回Dropbox访问令牌,将其提供给Dropbox SDK,然后将文件上传到Dropbox,所有这些都不需要客户端自己进行OAuth2流程。
你可以在GitHub上找到这个应用程序的样本,包括用于配置APIM的脚本和Blazor/C#版本。要了解更多关于Blazor版本的信息,请看我的同事Justin Yoo写的这篇文章。
不要忘了阅读API管理中的授权文档,并让我们知道你会发现它有哪些用途。










