識別提供者 Proxy

本檔說明如何建立 Proxy,以與使用 OAuth2 通訊協定的自訂或進階識別提供者互動。

Bot Framework 可讓使用者使用使用 OAuth2 通訊協定的各種識別提供者來登入。 不過,識別提供者可以藉由提供更進階的功能或替代的登入選項,從核心 OAuth2 通訊協定變差。 在這些情況下,您可能找不到適合您的適當 連線設定組態 。 可能的解決方法是執行下列動作:

  1. 撰寫 Bot Framework 權杖服務與更自訂或進階識別提供者之間的OAuth2 提供者 Proxy
  2. 設定連線設定以呼叫此 Proxy,並讓此 Proxy 呼叫自訂或進階識別提供者。 Proxy 也可以對應或轉換回應,使其符合 Bot Framework 權杖服務的預期。

OAuth2 Proxy 服務

若要建置 OAuth2 Proxy 服務,您必須使用兩個 OAuth2 API 實作 REST 服務:一個用於授權,另一個用於擷取權杖。 以下是每個方法的 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'");
    }
}

Proxy 連線設定組態

執行OAuth2 Proxy 服務之後,您可以在 Azure AI Bot Service 資源上建立OAuth 服務提供者連線設定。 請遵循下面所述的步驟。

  1. 提供連線設定的名稱。
  2. 選取 [一般 Oauth 2 服務提供者]。
  3. 輸入連線的 用戶端識別碼用戶端密碼 。 這些值可能是由您的進階或自訂識別提供者提供,或者,如果您使用的身分識別提供者未使用用戶端識別碼和密碼,這些值可能只是您的 Proxy。
  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; }
    }
}