Serwer proxy dostawców tożsamości

W tym dokumencie wyjaśniono, jak utworzyć serwer proxy do interakcji z niestandardowymi lub zaawansowanymi dostawcami tożsamości korzystającymi z protokołu OAuth2.

Platforma Bot Framework umożliwia użytkownikom logowanie się przy użyciu różnych dostawców tożsamości korzystających z protokołu OAuth2. Jednak dostawcy tożsamości mogą odejść od podstawowego protokołu OAuth2, oferując bardziej zaawansowane możliwości lub alternatywne opcje logowania. W takich przypadkach nie można znaleźć odpowiedniej konfiguracji ustawienia połączenia , która działa dla Ciebie. Możliwe rozwiązanie polega na wykonaniu następujących czynności:

  1. Napisz serwer proxy dostawcy OAuth2 , który znajduje się między usługą tokenu Bot Framework i bardziej dostosowanym lub zaawansowanym dostawcą tożsamości.
  2. Skonfiguruj ustawienie połączenia, aby wywołać ten serwer proxy, a ten serwer proxy wykona wywołania niestandardowego lub zaawansowanego dostawcy tożsamości. Serwer proxy może również mapować lub przekształcać odpowiedzi, aby były zgodne z oczekiwaniami usługi tokenu Bot Framework.

Usługa serwera proxy OAuth2

Aby utworzyć usługę serwera proxy OAuth2, musisz zaimplementować usługę REST z dwoma interfejsami API OAuth2: jeden do autoryzacji i jeden do pobierania tokenu. Poniżej znajdziesz przykład języka C# każdej z tych metod i czynności, które można wykonać w tych metodach w celu wywołania niestandardowego lub zaawansowanego dostawcy tożsamości.

Autoryzowanie interfejsu API

Autoryzowany interfejs API to http GET , który autoryzuje obiekt wywołujący, generuje właściwość kodu i przekierowuje do identyfikatora URI przekierowania.

[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}");
}

Interfejs API tokenu

Interfejs API tokenu jest postem HTTP wywoływanym przez usługę tokenu Bot Framework. Usługa tokenu Bot Framework wyśle element client_id i client_secret w treści żądania. Te wartości powinny być weryfikowane i/lub przekazywane do niestandardowego lub zaawansowanego dostawcy tożsamości. Odpowiedź na to wywołanie jest obiektem JSON zawierającym access_token wartość i wygaśnięcia tokenu (wszystkie inne wartości są ignorowane). Jeśli dostawca tożsamości zwraca wartość lub inną id_token , którą chcesz zwrócić, wystarczy zamapować ją na access_token właściwość odpowiedzi przed zwróceniem.

[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'");
    }
}

Konfiguracja ustawienia połączenia serwera proxy

Po uruchomieniu usługi serwera proxy OAuth2 możesz utworzyć ustawienie połączenia dostawcy usług OAuth w zasobie usługi Azure AI Bot Service. Wykonaj kroki opisane poniżej.

  1. Nadaj nazwę ustawieniu połączenia.
  2. Wybierz dostawcę usługi Generic Oauth 2 .
  3. Wprowadź identyfikator klienta i klucz tajny klienta dla połączenia. Te wartości mogą być udostępniane przez zaawansowanego lub niestandardowego dostawcę tożsamości lub mogą być specyficzne tylko dla serwera proxy, jeśli używany dostawca tożsamości nie używa identyfikatora klienta i wpisu tajnego.
  4. W polu Adres URL autoryzacji należy skopiować adres interfejsu API REST autoryzacji, na przykład https://proxy.com/api/oauth/authorize.
  5. W polu Token and Refresh URL (Adres URL tokenu i odświeżania) należy skopiować adres interfejsu API REST tokenu, na przykład https://proxy.com/api/oauth/token. Adres URL wymiany tokenów jest prawidłowy tylko dla dostawców opartych na usłudze AAD, więc można je zignorować.
  6. Na koniec dodaj wszystkie odpowiednie zakresy.

OAuthController dla aplikacji internetowej 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; }
    }
}