教程:使用本机身份验证在 iOS/macOS 应用中调用多个 API
适用于:iOS (Swift) macOS (Swift)
本教程介绍如何在 iOS/macOS 应用中获取访问令牌并调用 API。 适用于 iOS/macOS 的 Microsoft 身份验证库 (MSAL) 本机身份验证 SDK 允许通过一次登录获取多个访问令牌。 借助此功能,你可获取一个或多个访问令牌,而无需用户重新进行身份验证。
本教程介绍如何执行下列操作:
- 获取一个或多个访问令牌。
- 调用 API
先决条件
- 完成使用本机身份验证在示例 iOS 移动应用中登录用户并调用 API 中的步骤。
- 完成教程:使用本机身份验证在 iOS/macOS 应用中添加登录和注销中的步骤。 本教程介绍如何使用本机身份验证在 iOS/macOS 应用中登录用户。
获取一个或多个访问令牌
MSAL 本机身份验证 SDK 可以存储多个访问令牌。 登录后,可以使用 getAccessToken(scope:)
函数获取访问令牌,并指定要授予的新访问令牌的范围。
使用以下代码片段声明和设置一组 API 范围的值:
let protectedAPIUrl1: String? = nil let protectedAPIUrl2: String? = nil let protectedAPIScopes1: [String] = [] let protectedAPIScopes2: [String] = [] var accessTokenAPI1: String? var accessTokenAPI2: String?
- 使用第一个 Web API 的 URL 初始化
protectedAPIUrl1
。 - 使用第二个 Web API 的 URL 初始化
protectedAPIUrl2
。 - 使用第一个 API 的范围定义
protectedAPIScopes1
,例如["api://<Resource_App_ID>/ToDoList.Read", "api://<Resource_App_ID>/ToDoList.ReadWrite"]
。 - 使用第二个 API 的范围定义
protectedAPIScopes2
,类似于protectedAPIScopes1
。 - 声明可选的字符串变量
accessTokenAPI1
和accessTokenAPI2
。
- 使用第一个 Web API 的 URL 初始化
使用以下代码片段让用户登录:
@IBAction func signInPressed(_: Any) { guard let email = emailTextField.text, let password = passwordTextField.text else { resultTextView.text = "Email or password not set" return } print("Signing in with email \(email) and password") showResultText("Signing in...") nativeAuth.signIn(username: email, password: password, delegate: self) }
signInPressed
方法处理登录按钮的按下。 它会检查电子邮件和密码字段是否已填写。 如果其中一个为空,则显示“未设置电子邮件或密码”。如果这两个字段均已填写,则会显示“正在登录...”,并使用nativeAuth
中的signIn
方法以及提供的电子邮件和密码启动登录。 因为未指定任何范围,所以SDK 会检索对默认 OIDC 范围 (openid、offline_access、profile) 有效的令牌。使用以下代码片段获取一个或多个访问令牌:
@IBAction func protectedApi1Pressed(_: Any) { guard let url = protectedAPIUrl1, !protectedAPIScopes1.isEmpty else { showResultText("API 1 not configured.") return } if let accessToken = accessTokenAPI1 { accessProtectedAPI(apiUrl: url, accessToken: accessToken) } else { accountResult?.getAccessToken(scopes: protectedAPIScopes1, delegate: self) let message = "Retrieving access token to use with API 1..." showResultText(message) print(message) } } @IBAction func protectedApi2Pressed(_: Any) { guard let url = protectedAPIUrl2, !protectedAPIScopes2.isEmpty else { showResultText("API 2 not configured.") return } if let accessToken = accessTokenAPI2 { accessProtectedAPI(apiUrl: url, accessToken: accessToken) } else { accountResult?.getAccessToken(scopes: protectedAPIScopes2, delegate: self) let message = "Retrieving access token to use with API 2..." showResultText(message) print(message) } }
这些
protectedApi1Pressed
和protectedApi2Pressed
方法管理为两组不同的范围获取访问令牌的过程。 它们首先确保正确配置了每个 API 的 URL 和范围。 如果 API 的访问令牌已可用,它将直接访问该 API。 否则,它会请求访问令牌,并通知用户正在进行的令牌检索过程。若要将访问令牌分配给
protectedAPIScopes1
和protectedAPIScopes2
,请使用以下代码片段:func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) { print("Access Token: \(result.accessToken)") if protectedAPIScopes1.allSatisfy(result.scopes.contains), let url = protectedAPIUrl1 { accessTokenAPI1 = result.accessToken accessProtectedAPI(apiUrl: url, accessToken: result.accessToken) } if protectedAPIScopes2.allSatisfy(result.scopes.contains(_:)), let url = protectedAPIUrl2 { accessTokenAPI2 = result.accessToken accessProtectedAPI(apiUrl: url, accessToken: result.accessToken) } showResultText("Signed in." + "\n\n" + "Scopes:\n\(result.scopes)" + "\n\n" + "Access Token:\n\(result.accessToken)") updateUI() } func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) { showResultText("Error retrieving access token: \(error.errorDescription ?? "No error description")") }
onAccessTokenRetrieveCompleted
方法将访问令牌输出到控制台。 然后,它检查protectedAPIScopes1
是否包含在结果范围内以及protectedAPIUrl1
是否可用,如果是,则使用 URL 和令牌设置accessTokenAPI1
并调用accessProtectedAPI
。 如果满足条件,它将为protectedAPIScopes2
和protectedAPIUrl2
执行类似的检查,更新accessTokenAPI2
并调用 API。 最后,该方法将显示一条消息,其中包含已登录状态、范围和访问令牌,并更新 UI。onAccessTokenRetrieveError
方法将显示一条错误消息,其中包含访问令牌检索错误的说明,如果未提供任何说明,则显示默认消息。
调用 API
使用以下代码片段调用 API:
func accessProtectedAPI(apiUrl: String, accessToken: String) {
guard let url = URL(string: apiUrl) else {
let errorMessage = "Invalid API url"
print(errorMessage)
DispatchQueue.main.async {
self.showResultText(errorMessage)
}
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error found when accessing API: \(error.localizedDescription)")
DispatchQueue.main.async {
self.showResultText(error.localizedDescription)
}
return
}
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode)
else {
DispatchQueue.main.async {
self.showResultText("Unsuccessful response found when accessing the API")
}
return
}
guard let data = data, let result = try? JSONSerialization.jsonObject(with: data, options: []) else {
DispatchQueue.main.async {
self.showResultText("Couldn't deserialize result JSON")
}
return
}
DispatchQueue.main.async {
self.showResultText("""
Accessed API successfully using access token.
HTTP response code: \(httpResponse.statusCode)
HTTP response body: \(result)
""")
}
}
task.resume()
}
accessProtectedAPI
方法使用提供的访问令牌将 GET 请求发送到指定的 API 终结点。 它使用“授权标头”中的令牌配置请求。 当它收到成功响应(HTTP 状态代码 200-299)时,它会反序列化 JSON 数据,并使用 HTTP 状态代码和响应正文更新 UI。 如果在请求或响应处理期间发生错误,它会在 UI 中显示错误消息。 此方法允许访问 API 1 或 API 2,具体取决于提供的 URL 和访问令牌。