SharePoint クラシック ベースからクレーム ベースのWindows 認証に置き換えた際の注意点

こんにちは SharePoint サポートの森 健吾 (kenmori) です。
今回の投稿では、SharePoint Web アプリケーションの認証プロバイダー設定を、クラシックベースの Windows 認証からクレーム ベースの Windows 認証に切り替えた際に生じるカスタム ソリューションへの影響について簡単にご紹介します。

SharePoint 2010 ではクラシックベース (既定) とクレームベースが選択可能でしたが、SharePoint 2013 では画面を見てもクレームベースしか選択できない設計となりました。このため、アップグレード シナリオなどで、クラシック ベースからクレームベースにアップグレードすることが多くなり、問題に直面しやすいと考え情報公開することに至っています。

従来型のクラシックベースでは Web アプリケーションごとに 1 つの認証プロバイダーを指定していました。しかし、クレームベースでは、Security Token Service (STS) が認証を仲介します。このことで、複数の認証プロバイダーを同時に Web アプリケーションに関連づけることができ、委任先の各認証プロバイダーがユーザー認証すれば STS が該当 Web アプリケーションに対するアクセス権を付与すべくクレーム トークンを発行するという動作になります。

簡単に説明すると上記の通りですが、スペースの限られたブログでクレームベースを深く説明することは難しいですので、詳細については TechNet などを確認ください。

タイトル : 認証方法を計画する (SharePoint Server 2010)
アドレス : https://technet.microsoft.com/ja-jp/library/cc262350(office.14).aspx

 

1. ユーザーアカウントの表記が変わる

クラシック ベースにおいて、Windows ユーザー アカウント名は Domain\User でした。クレーム ベースでは、アカウント名は i:0#.w|Domain\User という表記に変更となります。
アカウント名をもとに条件分岐するコードは稀であるにせよ、サイトにユーザーを自動で登録する処理など様々なソリューションが考えられます。そのような処理を実装している場合は、アカウント名の変更の影響をきちんと把握しておく必要があります。

命名規則を簡単に紹介すると、以下の様になります。 

命名規則

1 文字目 : "i" (メンバの場合) / "c" (ロールの場合)
2 文字目 : ":" (固定)
3 文字目 : "0" (固定)
4 文字目 : "#" (ユーザーの場合) / "-" (ロールの場合) / その他
5 文字目 : "." (文字列の場合)
6 文字目 : "w" (Windows 認証の場合) / "f" (Form 認証の場合) / その他
以降1 : "|" (Windows 認証の場合は省略)
以降2 : 例.aspnetmembership (発行元の名前 : Windows 認証の場合は省略)
以降3 : "|" (固定)
以降4 : 例.Admin1 (ユーザー名 / ロール名)

プログラムでユーザー名からエンコードされたプレフィックスを含むアカウント名を取得するのであれば、以下のような処理で可能です。ここで取得したアカウント名を使用して、ユーザーを登録 (SPUserCollection.Add) することも可能です。

Windows 認証の場合 (C#)

(ユーザーの場合)
// 変数の指定
string userIdentifier = "DOMAIN\\user";

// 実際の処理
SPClaim claim = SPClaimProviderManager.CreateUserClaim(userIdentifier,SPOriginalIssuerType.Windows);
string loginName = SPClaimProviderManager.Local.EncodeClaim(claim);

(グループの場合)
// 変数の指定
string userIdentifier = "DOMAIN\\group";

SPClaimProviderManager cpm = SPClaimProviderManager.Local;
SPClaim userClaim = cpm.ConvertIdentifierToClaim(userIdentifier, SPIdentifierTypes.WindowsSecurityGroupName);
string loginName = userClaim.ToEncodedString();

タイトル : SPClaimProviderManager Class
アドレス : https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.claims.spclaimprovidermanager(v=office.14).aspx

タイトル : SharePoint クレームベース ID
アドレス ; https://msdn.microsoft.com/ja-jp/library/ee535242(v=office.14).aspx

 

2. Windows ユーザートークンに関連したカスタマイズに対する影響

クラシックベースの Windows 認証では、ASP.NET 偽装により実行スレッドが Web アクセスしたユーザー権限に偽装されます。この原理を利用して、これまで Web パーツなどの様々なカスタム ソリューションにおいて、様々な処理が実装されてきたと思います。

2-1. Windows ユーザートークンを使用した問題の発生するシナリオ

シナリオ 1)
Kerberos 認証による委任の設定を構築した上で、外部サーバー リソース (SQL Server, ファイル サーバー、Web サービス等) にパススルーでアクセスする。 (C#)

String connectionString = "Data Source=DBServer;Initial Catalog=AdventureWorks;Integrated Security=SSPI;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
}

(解説)
クラシックベース認証で Windows 認証を構築した場合、SharePoint サーバーは ASP.NET 偽装の動作に基づき、Windows 資格情報を使用してユーザーが Web アプリケーションに対して認証後は、HTTP 要求スレッドはブラウザーにログインしたユーザーの実行権限で偽装されました。
そのため、Kerberos による委任を構築している前提があれば、現在の資格情報であるスレッド実行ユーザー アカウントを使用して第 2 のサーバーにアクセスすることができました。

ところが、クレームベース認証で Windows 認証を構築した際、Windows 資格情報を使用してユーザーが Web アプリケーションに対して認証後は、HTTP 要求スレッドは ClaimsIdentity として偽装され、スレッドの実行ユーザーは匿名ユーザーとなります。
ブラウザーでアクセスしたユーザーではなく匿名ユーザーとして HTTP 要求スレッドが動作するため、第 2 のサーバーにそのままアクセスする権限を獲得することはできません。
そのため、従来通りの方法では、Kerberos 認証を構築したパススルー認証を実施することができなくなります。対処策は後述いたします。


クラシックベースの Windows 認証では NTLM のダブルホップ制約を回避するため、Kerberos 委任を構成して第 2 のサーバーにアクセスするというソリューションが多く存在していました。

タイトル : ASP ページの認証問題のトラブルシューティング
アドレス : https://msdn.microsoft.com/ja-jp/library/ms180891(v=vs.90).aspx
参考箇所 : ダブルホップの問題

特にパススルーが必須となるシナリオの例として、第 2 のサーバー側で要求元のアクセス権を検証する必要がある場合や、要求元のユーザー アカウントに紐づく情報を返す必要がある場合 (ユーザー プロファイル, メール, カレンダー等) などがあります。
このようなシナリオでは、RevertToSelf (ASP.NET 偽装解除、RunWithElevatedPrivileges etc.) 、つまりアプリケーション プール実行アカウントへのスレッド偽装による回避ができません。
パススルー シナリオでは、SecureStoreService という回避策もありますが、要求元先で 1:1 のアカウント、パスワードのマッピングを維持することはとても運用工数がかかるため、広く選択されなかった経緯があります。
これらの要因から、Kerberos 委任によるパススルー認証をクレームベースにおいても使用したいという要望は、現在も多数存在するものと想定しています。

 

シナリオ 2)
現在の実行ユーザーが特定のセキュリティグループに所属しているか (WindowsPrincipal.IsInRole) を確認して、表示項目を絞り込む (C#)

WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent() );
wp.IsInRole(@"BUILTIN\Administrators");

WindowsPrincipal.IsInRole メソッドは、実行アカウントのトークン情報をもとにセキュリティ グループへの所属確認が可能です。トークンには所属するセキュリティ グループの一覧情報も含まれているため、AD に対するネットワーク接続なしにセキュリティ グループを確認できるため、パフォーマンス上低コストな実装として広く使用されてきた経緯があります。

しかし、残念ながら、上記のコードもクレームベース認証に移行後はそのままで動作することはありません。
上述の通り、クレームベース認証において、HTTP 要求スレッドは ClaimsIdentity として偽装されるため、コンテキスト情報から WindowsPrincipal を取得 (new WindowsPrincipal(WindowsIdenitity.GetCurrent())) しても匿名ユーザーとして認識されます。そのため、上記のコードはクラシックベースで想定した通りの動作になりません。

 

2-2. Claim To Windows Token Service (C2WTS) を使用した対処策

クレーム ベース認証環境において、クラシックベースで実施していたソリューションを実行する方法があります。継続して上記シナリオのような Windows トークンを使用したカスタマイズを実装したい場合は、C2WTS を使用してクレーム トークンから Windows トークンを変換して取得する処理を実装する方法が有効です。

C2WTS は、非 Windows セキュリティ トークンから UPN を抽出し、偽装レベルの  Windows セキュリティ トークンを生成する Windows Identity Foundation (WIF) の機能です。

2014/05/16 追記 制限付き委任を構成する

クラシックベースにおける委任シナリオでは、無制限の委任により第 2 のサーバーに認証情報を受け渡すことが可能です。
しかし、C2WTS による Windows トークンの取得時には、該当の設定のままでは他サーバーに対するアクセス許可を取得することができません。
クレーム トークンから Windows トークンに変換する際には、サービスアカウントの委任設定において、"制限付き委任" を構成することが必須となります。

1) ドメイン コントローラーにログインします。
2) Active Directory ユーザーとコンピューターで、Active Directory オブジェクトのプロパティを開きます。
 - 補足
  ドメイン ユーザーの場合は、該当のユーザーアカウントを指定します。
   ローカル システムなどのコンピュータ アカウントの場合は、コンピュータ アカウントを指定します。
3) [委任] タブを表示します。
4) [指定されたサービスへの委任でのみこのユーザーを信頼する] を選択します。
5) [任意の認証プロトコルを使う] を選択します。
6) [追加] ボタンをクリックします。
7) [ユーザーまたはコンピューター] を選択します。
8) 委任するサービスを実行中のサービス アカウントを選択します。 
9) 接続先の SPN を指定します。
   - 補足
     接続先サービス アカウントがローカル システムなどのコンピュータ アカウントでない場合は、SPN を別途登録した上でこの画面で利用可能なサービスを選択します。
10) [OK] をクリックします。
11) "このアカウントが委任された資格情報を提示できるサービス" に選択した SPN が表示されていることを確認します。
12) [OK] をクリックします。

上記手順を実施後、必要に応じて変更を加えたサービス アカウントで稼働しているプロセス (w3wp.exe や c2wts サービス) がありましたら、プロセスを再起動 (アプリケーション プールのリサイクル、サービスの再起動) します。

以下にサービスを有効化する方法を記載します。

C2WTS を開始する方法

1) [サーバーの全体管理] サイトにアクセスします
2) [システム設定] セクションの [サーバーのサービスの管理] をクリックします
3) [サーバーのサービス] 画面の右上 [サーバー] の設定が Web Front サーバーであることを確認します
4) "Claims to Windows Token Service" の "処理" の "開始" をクリックします
5) [サーバーのサービスの管理] 画面が再度表示された際、"状態" の内容 が "開始済み" になっていることを確認します
6) [サーバーのサービス] 画面の右上 [サーバー] の設定より、対象の Web Front サーバーを変更します
7) 手順 3) ~ 6) をファーム上の全ての Web Front サーバーに対してご実施ください

上記サービスを構成するにあたっては、以下の設定も同時に実施しておいた方が良いので合わせてご紹介します。

C2WTS が自動起動しない現象を防ぐ方法

1) C2WTS を実行するサーバーに、ローカルの管理者権限のあるユーザーでログオンします。
2) コマンド プロンプトを管理者として開きます。
3) 以下のコマンドを実行します。depend= の後に半角スペースが入ることに注意してください。

>sc config "c2wts" depend= CryptSvc

4) [スタート] - [管理ツール] - [サービス] をクリックします。
5) "Windows トークン サービスに対するクレーム" を探してダブルクリックします。
6) [依存関係] タブをクリックし、[このサービスが依存するシステム コンポーネント] に "Cryptographic Services" が表示されていることをご確認ください。
7) C2WTS を実行するサーバーが複数ある場合は、すべてのサーバーでこの手順を実施します。

詳細は以下のサイトをご参考にしてください。

タイトル : Claims to Windows Token Service (c2WTS) not starting after rebooting server
アドレス : https://support.microsoft.com/kb/2512597

C2WTS は既定はローカル システム アカウントで実行されますが、ドメイン ユーザーで実行する場合は追加手順が必要となります。以下をご確認ください。

タイトル : Windows トークン サービスに対するクレーム (C2WTS)
アドレス : https://technet.microsoft.com/ja-jp/library/hh231678.aspx
参考箇所 : C2WTS の構成に必要な基本的な手順

このサービスが有効化された後は、以下のようなコーディングでクレーム トークンから Windows トークンを生成することができます。

C2WTS によるトークン変換実装方法 (C#)

IClaimsIdentity ci = System.Threading.Thread.CurrentPrincipal.Identity as IClaimsIdentity;
if (ci != null)
{
    if (ci.Claims.Count > 0)
    {
       //look for the UPN claim
       string upn = string.Empty;
       foreach (Microsoft.IdentityModel.Claims.Claim c in ci.Claims)
       {
           if (c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Upn)
           {

               upn = c.Value;
               break;
           }
       }
       WindowsIdentity wid = S4UClient.UpnLogon(upn);

       // シナリオ 1 ) 偽装
       using (WindowsImpersonationContext context = wid.Impersonate())
       {
           // ここに他サーバーに接続・要求する処理を書きます。
       }

       // シナリオ 2) セキュリティ グループ所属確認
       WindowsPrincipal wp = new WindowsPrincipal(wid);
       wp.IsInRole(@"BUILTIN\Administrators");
    }
}

2014/05/16 追記 SharePoint オブジェクトモデルを使用した C2WTS によるトークン変換実装方法 (C#)

上記のような WIF のコードをラップした SharePoint オブジェクト モデルによる実装方法も確認できましたので、下記に記載します。

WindowsIdentity wid = WindowsIdentity.GetCurrent();
SPSecurity.RunWithElevatedPrivileges(delegate()
{
     wid = SPSecurityContext.GetWindowsIdentity();
});
// シナリオ 1 ) 偽装
using (WindowsImpersonationContext context = wid.Impersonate())
{
    // ここに他サーバーに接続・要求する処理を書きます。

}
// シナリオ 2) セキュリティ グループ所属確認
WindowsPrincipal wp = new WindowsPrincipal(wid);
wp.IsInRole(@"BUILTIN\Administrators");

 

上記に記載した通り取得した WindowsIdentity クラスのインスタンスを使用し、Impersonate メソッドを実行して WindowsImpersonationContext オブジェクトを生成して偽装する、セキュリティ グループへの所属を確認する (WindowsPrincipal.IsInRole) といった従来のクラシックベース認証で実施できていたカスタマイズ コードが実装可能となります。

タイトル : WindowsImpersonationContext クラス
アドレス : https://msdn.microsoft.com/ja-jp/library/vstudio/system.security.principal.windowsimpersonationcontext(v=vs.90).aspx

タイトル : WindowsPrincipal.IsInRole メソッド (String)
アドレス : https://msdn.microsoft.com/ja-jp/library/vstudio/fs485fwh(v=vs.90).aspx 

タイトル : Claims to Windows Token Service (c2WTS) の概要
アドレス : https://msdn.microsoft.com/ja-jp/library/ee517278.aspx

タイトル : c2WTS からトークンを要求する方法
アドレス : https://msdn.microsoft.com/ja-jp/library/ee517258.aspx 

 

補足
Excel Service や BI 機能では、以下のホワイト ペーパーに C2WTS を使用した委任の構成について手順を含めた方法が紹介されています。併せて参考にしていただき、知識を深めていただけますと幸いです。

タイトル : Microsoft SharePoint 2010 製品の Kerberos 認証の構成
アドレス : https://www.microsoft.com/ja-jp/download/details.aspx?id=23176 

タイトル : Excel Services の ID 委任 (SharePoint Server 2010)
アドレス : https://technet.microsoft.com/ja-jp/library/gg502605(v=office.14).aspx

いかがでしたでしょうか。是非ともアップグレードなどの移行シナリオにおいて、事前にご確認いただき、運用への影響を抑えていただけますと幸いです。