Identitätsanbieterproxy

In diesem Dokument wird erläutert, wie Sie einen Proxy für die Interaktion mit benutzerdefinierten oder erweiterten Identitätsanbietern erstellen, die das OAuth2-Protokoll verwenden.

Das Bot Framework ermöglicht Benutzern die Anmeldung mit verschiedenen Identitätsanbietern, die das OAuth2-Protokoll verwenden. Identitätsanbieter können jedoch vom OAuth2-Kernprotokoll abweichen, indem sie erweiterte Funktionen oder alternative Anmeldeoptionen anbieten. In diesen Fällen finden Sie möglicherweise keine geeignete Konfiguration der Verbindungseinstellung , die für Sie geeignet ist. Eine mögliche Lösung besteht darin, folgendes zu tun:

  1. Schreiben Sie einen OAuth2-Anbieterproxy , der sich zwischen dem Bot Framework-Tokendienst und dem benutzerdefinierteren oder erweiterten Identitätsanbieter befindet.
  2. Konfigurieren Sie die Verbindungseinstellung, um diesen Proxy aufzurufen, und lassen Sie diesen Proxy die Aufrufe an den benutzerdefinierten oder erweiterten Identitätsanbieter ausführen. Der Proxy kann auch Antworten zuordnen oder transformieren, damit sie den Erwartungen des Bot Framework-Tokendiensts entsprechen.

OAuth2-Proxydienst

Zum Erstellen eines OAuth2-Proxydiensts müssen Sie einen REST-Dienst mit zwei OAuth2-APIs implementieren: eine für die Autorisierung und eine zum Abrufen eines Tokens. Unten finden Sie ein C#-Beispiel für jede dieser Methoden und was Sie in diesen Methoden tun können, um einen benutzerdefinierten oder erweiterten Identitätsanbieter aufzurufen.

Autorisieren der API

Die Autorisierungs-API ist ein HTTP GET , das den Aufrufer autorisiert, eine Codeeigenschaft generiert und an den Umleitungs-URI umleitet.

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

Token-API.

Die Token-API ist eine HTTP POST , die vom Bot Framework-Tokendienst aufgerufen wird. Der Bot Framework-Tokendienst sendet und client_idclient_secret im Text der Anforderung. Diese Werte sollten überprüft und/oder an den benutzerdefinierten oder erweiterten Identitätsanbieter übergeben werden. Die Antwort auf diesen Aufruf ist ein JSON-Objekt, das den access_token Ablaufwert und den Ablaufwert des Tokens enthält (alle anderen Werte werden ignoriert). Wenn Ihr Identitätsanbieter einen oder einen id_token anderen Wert zurückgibt, den Sie stattdessen zurückgeben möchten, müssen Sie ihn lediglich der access_token Eigenschaft Ihrer Antwort zuordnen, bevor Sie zurückkehren.

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

Konfiguration der Proxyverbindungseinstellung

Nachdem Sie Ihren OAuth2-Proxydienst ausgeführt haben, können Sie eine OAuth-Dienstanbieterverbindungseinstellung für Ihre Azure AI-Bot Service-Ressource erstellen. Führen Sie die unten beschriebenen Schritte aus.

  1. Geben Sie der Verbindungseinstellung einen Namen.
  2. Wählen Sie den generischen Oauth 2-Dienstanbieter aus.
  3. Geben Sie eine Client-ID und einen geheimen Clientschlüssel für die Verbindung ein. Diese Werte werden möglicherweise von Ihrem erweiterten oder benutzerdefinierten Identitätsanbieter bereitgestellt, oder sie können nur für Ihren Proxy spezifisch sein, wenn der von Ihnen verwendete Identitätsanbieter die Client-ID und den geheimen Schlüssel nicht verwendet.
  4. Für die Autorisierungs-URL sollten Sie die Adresse Ihrer Autorisierungs-REST-API kopieren, z. B https://proxy.com/api/oauth/authorize. .
  5. Für die Token- und Aktualisierungs-URL sollten Sie die Adresse Ihrer Token-REST-API kopieren, z. B https://proxy.com/api/oauth/token. . Die Tokenaustausch-URL ist nur für AAD-basierte Anbieter gültig und kann daher ignoriert werden.
  6. Fügen Sie schließlich alle geeigneten Bereiche hinzu.

OAuthController für ASP.NET Web-App

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