标识提供者代理
本文档介绍如何创建一个代理,以便与使用 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; }
}
}