Web API 帮助程序代码:Authentication 类

 

发布日期: 2017年1月

适用于: Dynamics 365 (online),Dynamics 365 (on-premises),Dynamics CRM 2016,Dynamics CRM Online

使用 Authentication 类协助建立与 Microsoft Dynamics 365 Web 服务之间经过验证的连接。 此类支持两种身份验证协议:Windows 身份验证(适用于 Dynamics 365 本地部署)或 OAuth 2.0(适用于 Dynamics 365(在线)或面向 Internet 的部署 (IFD))。 此类依赖 Microsoft Azure Active Directory 身份验证库 (ADAL) 处理 OAuth 协议。

Authentication 类位于 CRM SDK Web API 帮助程序库的 Authentication.cs 文件中。 它设计为与 Configuration 帮助程序库层次结构结合使用,供您通过类型为 System.Net.Http.HttpMessageHandler 的对象建立与 Dynamics 365 服务之间的安全连接。 有关详细信息,请参阅使用 Microsoft Dynamics 365 Web API 帮助程序库 (C# 库)

处理身份验证

Authentication 类用于通过 Dynamics 365 服务执行身份验证的机制取决于您通过 Configuration 参数传递到构造函数中的信息。 它尝试生成 HttpMessageHandler 派生的对象,然后您可以使用该对象实例化 System.Net.Http.HttpClient 实例以提供与 Dynamics 365 Service 之间安全、持久的通信会话。

首先执行与指定的 Dynamics 365 服务之间简短的发现握手,以确定正在使用的是 OAuth 或 Windows 本机身份验证。

  • 如果使用 OAuth,则使用握手中发现的身份验证授权创建一个 OAuthMessageHandler 对象。 此类派生自 System.Net.Http.DelegatingHandler,用于对每个请求刷新 OAuth 访问标记,这样您就不必显式管理标记到期。

  • 如果使用 Windows 身份验证,并且提供了用户凭据,则使用这些凭据构造 HttpClientHandler

  • 如果使用 Windows 身份验证,但是未提供用户凭据,则使用默认网络凭据构造 HttpClientHandler。

类层次结构和成员

下表显示 Authentication 类的公共成员。

Dynamics 365 Web API 帮助程序库 - 身份验证类关系图

身份验证类

属性:

Authority – 用于托管 OAuth 身份验证的服务器的 URL。

ClientHandlerHttpMessageHandler 派生的对象,该对象为消息请求提供网络凭据或授权访问标记。

Context – 身份验证事件的 AuthenticationContext


方法:

AquireToken – 对于 OAuth,为当前身份验证上下文返回一个 AuthenticationResult,其中包含刷新和访问标记。

Authentication – 使用 Configuration 参数初始化该类的实例。

DiscoverAuthority – 发现 Dynamics 365 Web 服务的身份验证授权。


OAuthMessageHandler 类

这个嵌套类设置为部署 Dynamics 365(在线)和 IFD 发送的每个消息的授权头。

使用情况

ConfigurationAuthentication 类设计为先后使用来建立与目标 Dynamics 365 服务之间的安全连接。 首先创建一个类型为 Configuration 的对象,然后将其作为单个参数传递给 Authentication 构造函数。 成功创建之后,可使用 ClientHandler 属性构造与 Dynamics 365 服务之间经过身份验证的安全、持久的 HTTP 客户端连接。

一种常用于完成此操作的方法(也是大多数 Web API C# 示例使用的方法)是,使用派生类 FileConfiguration 从正确撰写的应用程序配置文件读取连接信息,如以下代码行所示。

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

有关这种使用方式的详细信息,请参阅 FileConfiguration 连接设置一节。 尽管 Authentication 类包含其他多个公共属性和方法,提供它们的主要目的却是支持创建 ClientHandler 属性,并且很少被大多数客户端应用程序直接访问。

类列表

CRM SDK Web 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 Web API (C#) 入门
在 Visual Studio (C#) 中启动 Dynamics 365 Web API 项目
使用 Microsoft Dynamics 365 Web API 帮助程序库 (C# 库)
Web API 帮助程序代码:配置类
Web API 帮助程序代码:CrmHttpResponseException 类

Microsoft Dynamics 365

© 2017 Microsoft。 保留所有权利。 版权