다음을 통해 공유


ID 공급자 프록시

이 문서에서는 OAuth2 프로토콜을 사용하는 사용자 지정 또는 고급 ID 공급자와 상호 작용하는 프록시를 만드는 방법을 설명합니다.

Bot Framework를 사용하면 사용자가 OAuth2 프로토콜을 사용하는 다양한 ID 공급자를 사용하여 로그인할 수 있습니다. 그러나 ID 공급자는 고급 기능 또는 대체 로그인 옵션을 제공하여 핵심 OAuth2 프로토콜에서 벗어날 수 있습니다. 이러한 경우 적합한 적절한 연결 설정 구성 을 찾을 수 없습니다. 가능한 해결 방법은 다음을 수행하는 것입니다.

  1. Bot Framework 토큰 서비스와 더 사용자 지정된 또는 고급 ID 공급자 사이에 있는 OAuth2 공급자 프록시를 작성합니다.
  2. 이 프록시를 호출하도록 연결 설정을 구성하고 이 프록시가 사용자 지정 또는 고급 ID 공급자를 호출하도록 합니다. 또한 프록시는 응답을 매핑하거나 변환하여 Bot Framework 토큰 서비스에서 기대하는 내용을 준수하도록 할 수 있습니다.

OAuth2 프록시 서비스

OAuth2 프록시 서비스를 빌드하려면 두 개의 OAuth2 API를 사용하여 REST 서비스를 구현해야 합니다. 하나는 권한 부여용이고 다른 하나는 토큰 검색용입니다. 아래에서는 이러한 각 메서드의 C# 예제와 사용자 지정 또는 고급 ID 공급자를 호출하기 위해 이러한 메서드에서 수행할 수 있는 작업을 찾을 수 있습니다.

API 권한 부여

권한 부여 API는 호출자에게 권한을 부여하고, 코드 속성을 생성하고, 리디렉션 URI로 리디렉션하는 HTTP GET 입니다.

[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_secret 를 보냅니 client_id 다. 이러한 값의 유효성을 검사하고 사용자 지정 또는 고급 ID 공급자에 전달해야 합니다. 이 호출에 대한 응답은 토큰의 및 만료 값을 포함하는 access_token JSON 개체입니다(다른 모든 값은 무시됨). ID 공급자가 반환하려는 또는 다른 값을 반환 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 프록시 서비스를 실행한 후에는 Azure AI Bot Service 리소스에서 OAuth 서비스 공급자 연결 설정을 만들 수 있습니다. 아래에 설명된 단계를 따릅니다.

  1. 연결 설정에 이름을 지정합니다.
  2. 일반 Oauth 2 서비스 공급자를 선택합니다.
  3. 연결에 대한 클라이언트 ID클라이언트 암호를 입력합니다. 이러한 값은 고급 또는 사용자 지정 ID 공급자가 제공하거나 사용 중인 ID 공급자가 클라이언트 ID 및 비밀을 사용하지 않는 경우 프록시에만 해당될 수 있습니다.
  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 웹앱용 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; }
    }
}