Прокси-сервер для поставщиков удостоверений

В этом документе объясняется, как создать прокси-сервер для взаимодействия с пользовательскими или расширенными поставщиками удостоверений, которые используют протокол OAuth2.

Bot Framework позволяет пользователям выполнять вход с помощью различных поставщиков удостоверений, использующих протокол OAuth2. Однако поставщики удостоверений могут отличаться от основного протокола OAuth2, предлагая более сложные возможности или альтернативные варианты входа. В таких случаях может не найти подходящую конфигурацию параметров подключения , которая подходит вам. Возможное решение — сделать следующее:

  1. Создайте прокси-сервер поставщика OAuth2 , который находится между службой маркеров Bot Framework и более настраиваемым или расширенным поставщиком удостоверений.
  2. Настройте параметр подключения для вызова этого прокси-сервера и предоставьте этому прокси-серверу выполнять вызовы к пользовательскому или расширенному поставщику удостоверений. Прокси-сервер также может сопоставлять или преобразовывать ответы, чтобы они соответствовали ожиданиям службы токенов Bot Framework.

Прокси-служба OAuth2

Чтобы создать прокси-службу OAuth2, необходимо реализовать службу REST с двумя API OAuth2: один для авторизации и один для получения маркера. Ниже приведен пример каждого из этих методов на 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 токена — это HTTP POST , вызываемый службой токенов Bot Framework. Служба токенов 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 вы можете создать параметр подключения поставщика служб OAuth в ресурсе azure AI Служба Bot. Выполните описанные ниже действия.

  1. Присвойте параметру подключения имя.
  2. Выберите поставщика услуг Generic OAuth 2 .
  3. Введите идентификатор клиента и секрет клиента для подключения. Эти значения могут быть предоставлены расширенным или настраиваемым поставщиком удостоверений или только для вашего прокси-сервера, если используемый поставщик удостоверений не использует идентификатор клиента и секрет.
  4. В качестве URL-адреса авторизации следует скопировать адрес REST API авторизации, например https://proxy.com/api/oauth/authorize.
  5. В поле Маркер и URL-адрес обновления необходимо скопировать адрес REST API маркера, например https://proxy.com/api/oauth/token. URL-адрес обмена маркерами действителен только для поставщиков на основе AAD, поэтому его можно игнорировать.
  6. Наконец, добавьте все подходящие области.

OAuthController для веб-приложения ASP.NET

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; }
    }
}