Compartilhar via


Dynamics CRM 2011 トークンの有効期限と更新

みなさん、こんにちは。

今回は SDK プログラムで欠かせないサービスへの接続に関するTips です。

Dynamics CRM 2011 は WCF ベースのサービスを提供するため、数多くのタイムアウト設定が
ありますが、意外と見落としがちなのが、トークンの有効期限です。トークンの期限が切れた
状態でサービスのメソッドを実行すると、エラーになってしまいます。

トークンの有効期限確認

早速以下の手順で Dynamics CRM オンラインに対してサービスを生成してみます。

1. Visual Studio 2010 より、新規プロジェクトを作成。コンソールアプリを選択してください。
プロジェクト作成後、利用する .NET Framework を .NET Framework 4.0 にします。

image

2. 参照設定より以下の内容を追加します。

参照
Microsoft.Xrm.Sdk.dll アセンブリ

.NET
System.ServiceModel
System.Runtime.Serialization
System.Security
Microsoft.IdentityModel
System.IdentityModel

3. 既存の項目として以下のファイルを追加します。

sdk\samplecode\cs\helpercode\crmservicehelpers.cs
sdk\samplecode\cs\helpercode\deviceidmanager.cs

4. Program.cs を以下のように編集します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using Microsoft.IdentityModel;

using Microsoft.Crm.Sdk.Samples;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;

using System.Threading;

namespace TokenLife
{
    class Program
    {
        private OrganizationServiceProxy _serviceProxy;
      
        static void Main(string[] args)
        {
            // ヘルパークラスを利用して認証情報等収集
            ServerConnection serverConnect = new ServerConnection();
            ServerConnection.Configuration config = serverConnect.GetServerConfiguration();

            Program app = new Program();
            app.Run(config, true);
        }

        public void Run(ServerConnection.Configuration serverConfig, bool promptForDelete)
        {
            try
            {
                // ヘルパークラスを利用して、組織サービスの生成 
               using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri,
                                                                    serverConfig.HomeRealmUri,
                                                                    serverConfig.Credentials,
                                                                    serverConfig.DeviceCredentials))
                {
                    // WhoAmI リクエストを実行
                    WhoAmIRequest req= new WhoAmIRequest();
                    _serviceProxy.Execute(req);

                    if (null != _serviceProxy.SecurityTokenResponse)
                    {
                        // トークンの有効範囲を表示
                        var Token = _serviceProxy.SecurityTokenResponse.Token;
                        Console.WriteLine("Token Start at: " + Token.ValidFrom.ToLocalTime().ToString());
                        Console.WriteLine("Token Expire at: " + Token.ValidTo.ToLocalTime().ToString());
                    }                   
                }
            }
            catch
            {
            }
        }
    }
}

5. 最後の Console.WriteLine にブレークポイントを仕掛けて、実行します。

6. 今回はオンラインで検証したため、CRM サーバー名は crm5.dynamics.com
ユーザー名は Live ID を指定して、実行。組織の一覧から、実行対象を選択。

image

7. 実行結果からトークンは 8 時間有効であることが確認できます。
image

トークンの更新

1. 先ほどのメソッドの一部を以下のように書き換えます。

// ヘルパークラスを利用して、組織サービスの生成
using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri,
                                                        serverConfig.HomeRealmUri,
                                                        serverConfig.Credentials,
                                                        serverConfig.DeviceCredentials))
    {

        // WhoAmI リクエストを実行
        WhoAmIRequest req= new WhoAmIRequest();
        _serviceProxy.Execute(req);

        if (null != _serviceProxy.SecurityTokenResponse)
        {
            // トークンの有効範囲を表示
            var Token = _serviceProxy.SecurityTokenResponse.Token;
            Console.WriteLine("Token Start at: " + Token.ValidFrom.ToLocalTime().ToString());
            Console.WriteLine("Token Expire at: " + Token.ValidTo.ToLocalTime().ToString());
        }

        // 10 秒スリープ
        Thread.Sleep(10000);

        // トークンの更新
        _serviceProxy.Authenticate();

        if (null != _serviceProxy.SecurityTokenResponse)
        {
            // トークンの有効範囲を表示
            var Token = _serviceProxy.SecurityTokenResponse.Token;
            Console.WriteLine("Token Start at: " + Token.ValidFrom.ToLocalTime().ToString());
            Console.WriteLine("Token Expire at: " + Token.ValidTo.ToLocalTime().ToString());
        }         
    }

2. 最後の Console.WriteLine にブレークポイントを設定してから、
再度プログラムを実行します。今回はヘルパークラスを利用して
接続を作成しているため、2 回目の実行では前回使用した情報を
再利用できます。

image

3. 実行結果からトークンが更新されていることが確認できます。

image

トークンの自動更新

随時 Authenticate をしていけばトークンは更新されますが、各処理の
前にコードを入れていくことは煩わしいですし、不要な更新が増えることにも
なります。そこで最新の SDK にもあるように、独自のクラスを利用してその
処理を自動化してみます。

1. プロジェクトに新しくクラスを追加します。名前はMyOrganizationServiceProxy.cs
としてみました。任意で結構です。

2. 以下のように OrganizationServiceProxy を継承します。

class MyOrganizationServiceProxy : OrganizationServiceProxy

3. 変数の宣言とクラスのコンストラクタを作成します。単純に継承元の
コンストラクタを呼出して、インスタンスに代入しているだけです。

private MyOrganizationServiceProxy _serviceProxy;

public MyOrganizationServiceProxy(Uri serviceUri, Uri homeRealmUri,
ClientCredentials userCredentials, ClientCredentials deviceCredentials)
    : base(serviceUri, homeRealmUri, userCredentials, deviceCredentials)
{
    _serviceProxy = this;
}

4. ValidateAuthentication を上書きします。ValidateAuthentication
の詳細は SDK を参照してください。また上書きした中でトークンの更新用
メソッドを追加します。

protected override void ValidateAuthentication()
{
    // トークンの更新
    RenewTokenIfRequired();
    base.ValidateAuthentication();
}

5. トークンの更新用メソッドを追加します。ここでプロキシの SecurityTokenResponse
を確認して、存在する場合にはトークンの有効期限を確認しています。残り 15 分以下、
または、既に期限が切れている場合に、Authenticate メソッドを呼び出してトークンを
更新します。

public void RenewTokenIfRequired()
{
    if (null != this._serviceProxy.SecurityTokenResponse &&
        DateTime.Now.ToUniversalTime().AddMinutes(15) >=
          this._serviceProxy.SecurityTokenResponse.Response.Lifetime.Expires)
    {
        try
        {
            this._serviceProxy.Authenticate();
        }
        catch (CommunicationException)
        {
            if (null == this._serviceProxy.SecurityTokenResponse ||
                DateTime.Now >= this._serviceProxy.SecurityTokenResponse.Response.Lifetime.Expires)
            {
                throw;
            }
        }
    }
}

6. Program.cs に戻り、新しく作成したクラスを利用するよう一部コードを変更します。

変数宣言部
// private OrganizationServiceProxy _serviceProxy;
private MyOrganizationServiceProxy _serviceProxy;

プロキシ生成部
//using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri,
//                                                                serverConfig.HomeRealmUri,
//                                                                serverConfig.Credentials,
//                                                                serverConfig.DeviceCredentials))
using (_serviceProxy = new MyOrganizationServiceProxy(serverConfig.OrganizationUri,
                                                                    serverConfig.HomeRealmUri,
                                                                    serverConfig.Credentials,
                                                                    serverConfig.DeviceCredentials))

自動更新にするため Authenticate() は不要
// _serviceProxy.Authenticate();

7. 違いを見るために、以下の 2 箇所にブレークポイントを仕掛けます。

- 各サービスメソッド実行部分
- MyOrganizationServiceProxy.cs の ValidateAuthentication 内部

8. プログラムを実行します。サービスのメソッドが実行されるタイミングで、新しく作成した
クラスの ValidateAuthentication が呼び出され、トークンの更新が必要か確認します。

あとはオンラインの場合には 8 時間、クレーム認証の場合には ADFS のトークン生存
時間を目処に検証してみてください。

まとめ

今回のコードは プラグインレジストレーションツールにあるものから借用しただけのもので、
完全なコードは以下のファイルにあります。実際に利用する場合には、これらのファイルを
確認していただき、必要な対処を行ってください。

sdk\tools\pluginregistration フォルダ
autorefreshsecuritytoken.cs
toolserviceproxies.cs

参考情報:
Claims-based authentication and security token expiration
https://technet.microsoft.com/en-us/library/gg188586.aspx

- Dynamics CRM サポート 中村 憲一郎