你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

标识提供者代理

本文档介绍如何创建一个代理,以便与使用 OAuth2 协议的自定义或高级标识提供者交互。

Bot Framework 允许用户使用使用 OAuth2 协议的各种标识提供者登录。 但是,标识提供者提供更高级的功能或替代性的登录选项,因此可能与核心 OAuth2 协议有所背离。 在这种情况下,你可能找不到适合自己的连接设置配置。 一种可能的解决方案是执行以下操作:

  1. 编写位于 Bot Framework 令牌服务与更自定义或高级标识提供者之间的 OAuth2 提供程序代理
  2. 配置连接设置以调用此代理,并让此代理调用自定义或高级标识提供者。 该代理还可以映射或转换响应,使其符合 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_idclient_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 服务提供程序连接设置。 请按照以下步骤操作。

  1. 为连接设置命名。
  2. 选择“通用 Oauth 2”服务提供程序。
  3. 输入连接的 客户端 ID客户端密码 。 这些值可能由高级标识提供者或自定义标识提供者提供,或者,如果所使用的标识提供者不使用客户端 ID 和机密,则这些值可能仅特定于代理。
  4. 对于“授权 URL”,应复制授权 REST API 的地址,例如 https://proxy.com/api/oauth/authorize
  5. 对于“令牌和刷新 URL”,应复制令牌 REST API 的地址,例如 https://proxy.com/api/oauth/token。 “令牌交换 URL”仅对基于 AAD 的提供程序有效,因此可以忽略。
  6. 最后,添加任何适用的范围。

适用于 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; }
    }
}