IdentitätsanbieterproxyIdentity providers proxy

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.This document explains how to create a proxy to interact with custom or advanced identity providers that use OAuth2 protocol.

Der Bot Framework-Tokendienst unterstützt einen Bot, der Benutzern bei der Anmeldung bei einer Vielzahl von Identitätsanbietern hilft, die das OAuth2-Protokoll verwenden.The Bot Framework token service supports a bot to help users when logging in a variety of identity providers that use the OAuth2 protocol. Identitätsanbieter können jedoch vom OAuth2-Kernprotokoll abweichen, indem sie erweiterte Funktionen oder alternative Anmeldeoptionen anbieten.However, identity providers can deviate from the core OAuth2 protocol, by offering more advanced capabilities, or alternative sign-in options. In diesen Fällen finden Sie möglicherweise keine geeignete Konfiguration der Verbindungseinstellung, die für Sie geeignet ist.In these cases, you may not find an appropriate connection setting configuration that works for you. Eine mögliche Lösung besteht darin, einen OAuth2-Anbieterproxy zu schreiben, der sich zwischen dem Bot Framework Tokendienst und dem stärker angepassten oder erweiterten Identitätsanbieter befindet.A possible solution is to write an OAuth2 provider proxy that is in between the Bot Framework token service and the more customized or advanced identity provider.

Sie können die Verbindungseinstellung so konfigurieren, dass dieser Proxy aufgerufen wird, und diesen Proxy die Aufrufe an den benutzerdefinierten oder erweiterten Identitätsanbieter vornehmen lassen.You can configure the connection setting to call this proxy, and have this proxy make the calls to the custom or advanced identity provider. Der Proxy kann auch Antworten zuordnen oder transformieren, damit sie dem entsprechen, was der Bot Framework Tokendienst erwartet.The proxy can also map or transform responses to make them conform to what the Bot Framework token service expects.

OAuth2-ProxydienstOAuth2 Proxy Service

Um einen OAuth2-Proxydienst zu erstellen, müssen Sie einen REST-Dienst mit zwei OAuth2-APIs implementieren: eine für die Autorisierung und eine zum Abrufen eines Tokens.To build an OAuth2 Proxy Service, you need to implement a REST service with two OAuth2 APIs: one for authorization and one for retrieving a token. Im Folgenden finden Sie ein C#-Beispiel für jede dieser Methoden und die Möglichkeiten, die Sie mit diesen Methoden ausführen können, um einen benutzerdefinierten oder erweiterten Identitätsanbieter aufzurufen.Below you will find a C# example of each of these methods and what you can do in these methods to call a custom or advanced identity provider.

Autorisieren der APIAuthorize API

Die Autorisierungs-API ist ein HTTP GET, das den Aufrufer autorisiert, eine Codeeigenschaft generiert und an den Umleitungs-URI umleitet.The authorize API is an HTTP GET that authorizes the caller, generates a code property, and redirects to the redirect 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}");
}

Token-API.Token API

Die Token-API ist ein HTTP POST, der vom Bot Framework Tokendienst aufgerufen wird.The Token API is an HTTP POST that is called by the Bot Framework token service. Der Bot Framework Tokendienst sendet client_id und client_secret im Anforderungstext.The Bot Framework token service will send the client_id and client_secret in the request’s body. Diese Werte sollten überprüft und/oder an den benutzerdefinierten oder erweiterten Identitätsanbieter übergeben werden.These values should be validated and/or passed along to the custom or advanced identity provider. Die Antwort auf diesen Aufruf ist ein JSON-Objekt, das den - und -Ablaufwert des Tokens enthält access_token (alle anderen Werte werden ignoriert).The response to this call is a JSON object containing the access_token and expiration value of the token (all other values are ignored). Wenn Ihr Identitätsanbieter stattdessen einen oder einen anderen Wert zurückgibt, den Sie zurückgeben möchten, müssen Sie ihn lediglich der -Eigenschaft Ihrer Antwort zuordnen, bevor Sie id_token access_token ihn zurückgeben.If your identity provider returns an id_token or some other value that you want to return instead, you just need to map it to the access_token property of your response before you return.

[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 ProxyverbindungseinstellungProxy Connection Setting Configuration

Sobald Ihr OAuth2-Proxydienst ausgeführt wird, können Sie eine OAuth-Dienstanbieter-Verbindungseinstellung für Ihre Azure Bot Service erstellen. Once you have your OAuth2 Proxy Service running, you can create an OAuth Service Provider Connection Setting on your Azure Bot Service resource. Führen Sie die unten beschriebenen Schritte aus.Follow the steps described below.

  1. Geben Sie der Verbindungseinstellung einen Namen.Give a name to the connection setting.
  2. Wählen Sie den generischen Oauth 2-Dienstanbieter aus.Select the Generic Oauth 2 service provider.
  3. Geben Sie client id an, welche client secret und welche geeignet sind.Include whatever client id and client secret that are appropriate. Möglicherweise sind dies die Werte ihres erweiterten/benutzerdefinierten Identitätsanbieters, oder diese können nur für Ihren Proxy spezifisch sein, wenn der von Ihnen verwendete Identitätsanbieter keine Client-ID und kein Geheimnis verwendet.Perhaps these are the values from your advanced/custom identity provider, or these could be specific just to your proxy if the identity provider you are using does not use client id and secret.
  4. Für die Autorisierungs-URL sollten Sie die Adresse Ihrer Autorisierungs-REST-API kopieren, z. https://proxy.com/api/oauth/authorize B. .For the Authorization URL, you should copy the address of your authorization REST API, for example https://proxy.com/api/oauth/authorize.
  5. Für die Token- und Aktualisierungs-URL sollten Sie die Adresse Ihrer Token-REST-API kopieren, z. https://proxy.com/api/oauth/token B. .For the Token and Refresh URL, you should copy the address of your token REST API, for example https://proxy.com/api/oauth/token. Die Tokenaustausch-URL ist nur für AAD-basierte Anbieter gültig und kann daher ignoriert werden.The Token Exchange URL is valid only for AAD based providers and so can be ignored.
  6. Fügen Sie abschließend alle geeigneten Bereiche hinzu.Finally, add any scopes that are appropriate.

OAuthController für .NET Core 3.1 ASP.NET Web-AppOAuthController for .NET Core 3.1 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; }
    }
}