다음을 통해 공유


웹 API 도우미 코드: 인증 클래스

 

게시 날짜: 2017년 1월

적용 대상: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online

Authentication 클래스를 사용하여 Microsoft Dynamics 365 웹 서비스에 대해 유효한 연결을 구축합니다. 이 클래스는 Dynamics 365 온-프레미스에 대한 Windows 인증 또는 Dynamics 365(온라인) 또는 인터넷 연결 배포(IFD)에 대한 OAuth 2.0 등 두 가지 인증 프로토콜을 지원합니다. 이 클래스는 Microsoft Azure Active Directory 인증 라이브러리(ADAL)를 기반으로 OAuth 프로토콜을 처리합니다.

Authentication 클래스는 CRM SDK 웹 API 도우미 라이브러리의 Authentication.cs 파일에 있습니다. 개체 유형 System.Net.Http.HttpMessageHandler를 통해 Dynamics 365 서비스에 대한 안전한 연결을 구축하도록 Configuration 도우미 클래스 계층 구조와 함께 작동하도록 설계되었습니다. 자세한 내용은 Microsoft Dynamics 365 웹 API 도우미 라이브러리(C#) 사용을 참조하십시오.

인증 처리

Authentication 클래스가 Dynamics 365 서비스 인증을 위해 사용하는 메커니즘은 Configuration 매개 변수로 생성자에 전달하는 정보에 따라 다릅니다. System.Net.Http.HttpClient 인스턴스를 인스턴스화하기 위해 사용할 수 있는 HttpMessageHandler에서 파생된 개체 구축을 시도합니다. 이로 인해 Dynamics 365 서비스와 안전하고 지속적인 통신 세션을 제공할 수 있습니다.

먼저 OAuth 또는 Windows 전용 인증의 사용 여부를 확인하기 위해 지정된 Dynamics 365 서비스에 대한 간단한 검색 핸드셰이크가 수행됩니다.

  • OAuth를 사용하는 경우 핸드셰이크에서 발견된 인증 기관을 사용하여 OAuthMessageHandler 개체가 만들어집니다. System.Net.Http.DelegatingHandler에서 파생된 이 클래스는 모든 요청에서 OAuth 액세스 토큰을 새로 고칩니다. 따라서 토큰 만료를 명시적으로 관리할 필요가 없습니다.

  • Windows 인증을 사용 중이며 및 사용자 자격 증명이 제공된 경우 이러한 자격 증명이 HttpClientHandler를 만들기 위해 사용됩니다.

  • Windows 인증을 사용 중이나 및 사용자 자격 증명이 제공되지 않은 경우 기본 네트워크 자격 증명을 사용하여 HttpClientHandler가 구축됩니다.

클래스 계층 구조 및 구성원

다음 표에서 Authentication 클래스의 공통 구성원을 보여줍니다.

Dynamics 365 웹 API 도우미 라이브러리-인증 클래스 다이어그램

인증 클래스

속성:

Authority - OAuth 인증을 관리하는 서버의 URL입니다.

ClientHandler - 메시지 요청에 대한 네트워크 자격 증명 또는 권한 부여 액세스 토큰을 제공하는 HttpMessageHandler에서 파생된 개체입니다.

Context - 인증 이벤트에 대한 AuthenticationContext입니다.


메서드:

AquireToken - OAuth의 경우 현재 인증 컨텍스트에 대한 새로 고침 및 액세스 토큰을 포함하는 AuthenticationResult를 반환합니다.

Authentication - Configuration 매개 변수를 사용하여 이 클래스의 인스턴스를 초기화합니다.

DiscoverAuthority - Dynamics 365 웹 서비스의 인증 기관을 검색합니다.


OAuthMessageHandler 클래스

이 중첩된 클래스는 Dynamics 365(온라인) 및 IFD 배포에 대해 보낸 각 메시지에 대해 인증 헤더를 설정합니다.

사용법

ConfigurationAuthentication 클래스는 대상 Dynamics 365 서비스에 대한 보안 연결을 설정하는 데 동시에 사용할 수 있도록 고안되었습니다. 먼저 Configuration 유형의 개체를 만든 다음 Authentication 생성자에 단일 매개 변수로 전달합니다. 성공적으로 만든 후 Dynamics 365 서비스에 대해 지속적인 인증된 보안 HTTP 클라이언트 연결을 구축하기 위해 ClientHandler 속성을 사용할 수 있습니다.

가장 일반적으로 대부분의 웹 API C# 샘플을 사용하는 이 작업을 수행하려면 다음 줄에서 볼 수 있듯이 파생된 클래스 FileConfiguration을 사용하여 적절히 작성된 응용 프로그램 구성 파일에서 연결 정보를 읽습니다.

FileConfiguration config = new FileConfiguration(null);
Authentication auth = new Authentication(config);
httpClient = new HttpClient(auth.ClientHandler, true);

이러한 사용 방법에 대한 자세한 내용은 FileConfiguration 연결 설정 섹션을 참조하십시오.Authentication 클래스에는 여러 다른 공용 속성 및 메서드가 포함되어 있지만 기본적으로 ClientHandler 속성을 만드는 작업을 지원하기 위해 제공되며 대부분의 클라이언트 응용 프로그램에서 직접 액세스할 수 없습니다.

클래스 목록

이 클래스에 대한 최신 정보는 CRM SDK 웹 API 도우미 라이브러리 NuGet 패키지에서 찾을 수 있습니다.

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;

namespace Microsoft.Crm.Sdk.Samples.HelperCode
{
    /// <summary>
    /// Manages user authentication with the Dynamics CRM Web API (OData v4) services. This class uses Microsoft Azure
    /// Active Directory Authentication Library (ADAL) to handle the OAuth 2.0 protocol. 
    /// </summary>
    public class Authentication
    {
        private Configuration _config = null;
        private HttpMessageHandler _clientHandler = null;
        private AuthenticationContext _context = null;
        private string _authority = null;

        #region Constructors
        /// <summary>
        /// Base constructor.
        /// </summary>
        public Authentication() { }

        /// <summary>
        /// Establishes an authentication session for the service.
        /// </summary>
        /// <param name="config">A populated configuration object.</param>
        public Authentication(Configuration config)
            : base()
        {
            if (config == null)
                throw new Exception("Configuration cannot be null.");

            _config = config;

            SetClientHandler();
        }

        /// <summary>
        /// Custom constructor that allows adding an authority determined asynchronously before 
        /// instantiating the Authentication class.
        /// </summary>
        /// <remarks>For a WPF application, first call DiscoverAuthorityAsync(), and then call this
        /// constructor passing in the authority value.</remarks>
        /// <param name="config">A populated configuration object.</param>
        /// <param name="authority">The URL of the authority.</param>
        public Authentication(Configuration config, string authority)
            : base()
        {
            if (config == null)
                throw new Exception("Configuration cannot be null.");

            _config = config;
            Authority = authority;

            SetClientHandler();
        }
        #endregion Constructors

        #region Properties
        /// <summary>
        /// The authentication context.
        /// </summary>
        public AuthenticationContext Context
        {
            get
            { return _context; }

            set
            { _context = value; }
        }

        /// <summary>
        /// The HTTP client message handler.
        /// </summary>
        public HttpMessageHandler ClientHandler
        {
            get
            { return _clientHandler; }

            set
            { _clientHandler = value; }
        }


        /// <summary>
        /// The URL of the authority to be used for authentication.
        /// </summary>
        public string Authority
        {
            get
            {
                if (_authority == null)
                    _authority = DiscoverAuthority(_config.ServiceUrl);

                return _authority;
            }

            set { _authority = value; }
        }
        #endregion Properties

        #region Methods
        /// <summary>
        /// Returns the authentication result for the configured authentication context.
        /// </summary>
        /// <returns>The refreshed access token.</returns>
        /// <remarks>Refresh the access token before every service call to avoid having to manage token expiration.</remarks>
        public AuthenticationResult AcquireToken()
        {
            if (_config != null && (!string.IsNullOrEmpty(_config.Username) && _config.Password != null))
            {
                UserCredential cred = new UserCredential(_config.Username, _config.Password);
                return _context.AcquireToken(_config.ServiceUrl, _config.ClientId, cred);
            }
            return _context.AcquireToken(_config.ServiceUrl, _config.ClientId, new Uri(_config.RedirectUrl),
                PromptBehavior.Auto);
        }

        /// <summary>
        /// Returns the authentication result for the configured authentication context.
        /// </summary>
        /// <param name="username">The username of a CRM system user in the target organization. </param>
        /// <param name="password">The password of a CRM system user in the target organization.</param>
        /// <returns>The authentication result.</returns>
        /// <remarks>Setting the username or password parameters to null results in the user being prompted to
        /// enter log-on credentials. Refresh the access token before every service call to avoid having to manage
        /// token expiration.</remarks>
        public AuthenticationResult AcquireToken(string username, SecureString password)
        {

            try
            {
                if (!string.IsNullOrEmpty(username) && password != null)
                {
                    UserCredential cred = new UserCredential(username, password);
                    return _context.AcquireToken(_config.ServiceUrl, _config.ClientId, cred);
                }
            }
            catch (Exception e)
            {
                throw new Exception("Authentication failed. Verify the configuration values are correct.", e);
            }
            return null;
        }


        /// <summary>
        /// Discover the authentication authority.
        /// </summary>
        /// <returns>The URL of the authentication authority on the specified endpoint address, or an empty string
        /// if the authority cannot be discovered.</returns>
         public static string DiscoverAuthority(string serviceUrl)
        {
            try
            {
                AuthenticationParameters ap = AuthenticationParameters.CreateFromResourceUrlAsync(
                    new Uri(serviceUrl + "api/data/")).Result;

                return ap.Authority;
            }
            catch (HttpRequestException e)
            {
                throw new Exception("An HTTP request exception occurred during authority discovery.", e);
            }
            catch (System.Exception e )
            {
                // This exception ocurrs when the service is not configured for OAuth.
                if( e.HResult == -2146233088 )
                {
                    return String.Empty;
                }
                else
                {
                    throw e;
                }
            }
        }

        /// <summary>
        /// Discover the authentication authority asynchronously.
        /// </summary>
        /// <param name="serviceUrl">The specified endpoint address</param>
        /// <returns>The URL of the authentication authority on the specified endpoint address, or an empty string
        /// if the authority cannot be discovered.</returns>
        public static async Task<string> DiscoverAuthorityAsync(string serviceUrl)
        {
            try
            {
                AuthenticationParameters ap = await AuthenticationParameters.CreateFromResourceUrlAsync(
                    new Uri(serviceUrl + "api/data/"));

                return ap.Authority;
            }
            catch (HttpRequestException e)
            {
                throw new Exception("An HTTP request exception occurred during authority discovery.", e);
            }
            catch (Exception e)
            {
                // These exceptions ocurr when the service is not configured for OAuth.

                // -2147024809 message: Invalid authenticate header format Parameter name: authenticateHeader
                if (e.HResult == -2146233088 || e.HResult == -2147024809)
                {
                    return String.Empty;
                }
                else
                {
                    throw e;
                }
            }
        }

        /// <summary>
        /// Sets the client message handler as appropriate for the type of authentication
        /// in use on the web service endpoint.
        /// </summary>
        private void SetClientHandler()
        {
            // Check the Authority to determine if OAuth authentication is used.
            if (String.IsNullOrEmpty(Authority))
            {
                if (_config.Username != String.Empty)
                {
                    _clientHandler = new HttpClientHandler()
                    { Credentials = new NetworkCredential(_config.Username, _config.Password, _config.Domain) };
                }
                else
                // No username is provided, so try to use the default domain credentials.
                {
                    _clientHandler = new HttpClientHandler()
                    { UseDefaultCredentials = true };
                }
            }
            else
            {
                _clientHandler = new OAuthMessageHandler(this, new HttpClientHandler());
                _context = new AuthenticationContext(Authority, false);
            }
        }
        #endregion Methods

        /// <summary>
        /// Custom HTTP client handler that adds the Authorization header to message requests. This
        /// is required for IFD and Online deployments.
        /// </summary>
        class OAuthMessageHandler : DelegatingHandler
        {
            Authentication _auth = null;

            public OAuthMessageHandler( Authentication auth, HttpMessageHandler innerHandler )
                : base(innerHandler)
            {
                _auth = auth;
            }

            protected override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
            {
                // It is a best practice to refresh the access token before every message request is sent. Doing so
                // avoids having to check the expiration date/time of the token. This operation is quick.
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _auth.AcquireToken().AccessToken);

                return base.SendAsync(request, cancellationToken);
            }
        }
    }
}

참고 항목

Microsoft Dynamics 365 웹 API(C#) 시작
Visual Studio(C#)에서 Dynamics 365 웹 API 프로젝트 시작
Microsoft Dynamics 365 웹 API 도우미 라이브러리(C#) 사용
웹 API 도우미 코드: 구성 클래스
웹 API 도우미 코드: CrmHttpResponseException 클래스

Microsoft Dynamics 365

© 2017 Microsoft. All rights reserved. 저작권 정보