Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
本文档介绍如何创建一个代理,以便与使用 OAuth2 协议的自定义或高级标识提供者交互。
Bot Framework 允许用户使用各种采用 OAuth2 协议的标识提供者进行登录。 但是,标识提供者提供更高级的功能或替代性的登录选项,因此可能与核心 OAuth2 协议有所背离。 在这种情况下,你可能找不到适合自己的连接设置配置。 一种可行的解决方案是采取以下措施:
- 编写一个 OAuth2 提供者代理,它位于 Bot Framework 令牌服务和更多自定义或高级标识提供者之间。
- 配置连接设置以调用该代理,并让该代理调用自定义或高级标识提供者。 该代理还可以映射或转换响应,使其符合 Bot Framework 令牌服务的预期。
OAuth2 代理服务
若要构建 OAuth2 代理服务,需使用两个 OAuth2 API 实现 REST 服务:一个 API 用于授权,另一个用于检索令牌。 下面是每个方法的 C# 示例,以及在这些方法中调用自定义或高级标识提供者的方法。
授权 API
授权 API 是一个 HTTP GET,它可为调用方授权,生成代码属性,并重定向到重定向 URI。
[HttpGet("authorize")]
public ActionResult Authorize(
string response_type,
string client_id,
string state,
string redirect_uri,
string scope = null)
{
// validate parameters
if (string.IsNullOrEmpty(state))
{
return BadRequest("Authorize request missing parameter 'state'");
}
if (string.IsNullOrEmpty(redirect_uri))
{
return BadRequest("Authorize request missing parameter 'redirect_uri'");
}
// redirect to an external identity provider,
// or for this sample, generate a code and token pair and redirect to the redirect_uri
var code = Guid.NewGuid().ToString("n");
var token = Guid.NewGuid().ToString("n");
_tokens.AddOrUpdate(code, token, (c, t) => token);
return Redirect($"{redirect_uri}?code={code}&state={state}");
}
令牌 API
令牌 API 是由 Bot Framework 令牌服务调用的 HTTP POST。 Bot Framework 令牌服务将在请求正文中发送 client_id 和 client_secret。 这些值应经过验证,并/或传递给自定义或高级标识提供者。
对此调用的响应是一个 JSON 对象,其中包含令牌的 access_token 和过期时间值(将忽略所有其他值)。 如果标识提供者返回 id_token 或你想要返回的其他某个值,则你只需在返回之前,将其映射到响应的 access_token 属性。
[HttpPost("token")]
public async Task<ActionResult> Token()
{
string body;
using (var reader = new StreamReader(Request.Body))
{
body = await reader.ReadToEndAsync();
}
if (string.IsNullOrEmpty(body))
{
return BadRequest("Token request missing body");
}
var parameters = HttpUtility.ParseQueryString(body);
string authorizationCode = parameters["code"];
string grantType = parameters["grant_type"];
string clientId = parameters["client_id"];
string clientSecret = parameters["client_secret"];
string redirectUri= parameters["redirect_uri"];
// Validate any of these parameters here, or call out to an external identity provider with them
if (_tokens.TryRemove(authorizationCode, out string token))
{
return Ok(new TokenResponse()
{
AccessToken = token,
ExpiresIn = 3600,
TokenType = "custom",
});
}
else
{
return BadRequest("Token request body did not contain parameter 'code'");
}
}
代理连接设置配置
运行 OAuth2 代理服务后,就可以在 Azure AI 机器人服务资源上创建 OAuth 服务提供商连接设置。 请按照以下步骤操作。
- 为连接设置命名。
- 选择“通用 Oauth 2”服务提供程序。
- 输入连接的客户端 ID 和客户端机密。 这些值可能由高级或自定义标识提供者提供,如果你使用的标识提供者未使用客户 ID 和机密,则这些值也可能是你的代理专用的。
- 对于“授权 URL”,应复制授权 REST API 的地址,例如
https://proxy.com/api/oauth/authorize。 - 对于“令牌和刷新 URL”,应复制令牌 REST API 的地址,例如
https://proxy.com/api/oauth/token。 “令牌交换 URL”仅对基于 AAD 的提供程序有效,因此可以忽略。 - 最后,添加任何适用的范围。
ASP.NET Web 应用的 OAuthController
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using System.Web;
namespace CustomOAuthProvider.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OAuthController : ControllerBase
{
ConcurrentDictionary<string, string> _tokens;
public OAuthController(ConcurrentDictionary<string, string> tokens)
{
_tokens = tokens;
}
[HttpGet("authorize")]
public ActionResult Authorize(
string response_type,
string client_id,
string state,
string redirect_uri,
string scope = null)
{
if (string.IsNullOrEmpty(state))
{
return BadRequest("Authorize request missing parameter 'state'");
}
if (string.IsNullOrEmpty(redirect_uri))
{
return BadRequest("Authorize request missing parameter 'redirect_uri'");
}
// reidrect to an external identity provider,
// or for this sample, generte a code and token pair and redirect to the redirect_uri
var code = Guid.NewGuid().ToString("n");
var token = Guid.NewGuid().ToString("n");
_tokens.AddOrUpdate(code, token, (c, t) => token);
return Redirect($"{redirect_uri}?code={code}&state={state}");
}
[HttpPost("token")]
public async Task<ActionResult> Token()
{
string body;
using (var reader = new StreamReader(Request.Body))
{
body = await reader.ReadToEndAsync();
}
if (string.IsNullOrEmpty(body))
{
return BadRequest("Token request missing body");
}
var parameters = HttpUtility.ParseQueryString(body);
string authorizationCode = parameters["code"];
string grantType = parameters["grant_type"];
string clientId = parameters["client_id"];
string clientSecret = parameters["client_secret"];
string redirectUri= parameters["redirect_uri"];
// Validate any of these parameters here, or call out to an external identity provider with them
if (_tokens.TryRemove(authorizationCode, out string token))
{
return Ok(new TokenResponse()
{
AccessToken = token,
ExpiresIn = 3600,
TokenType = "custom",
});
}
else
{
return BadRequest("Token request body did not contain parameter 'code'");
}
}
}
public class TokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("id_token")]
public string IdToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
}
}