WCF サービスを呼び出した際に、以下のエラーが発生することがあります。
System.ServiceModel.Security.SecurityNegotiationException:
The caller was not authenticated by the service.
このエラーは、ビジネスロジックが実行される前に、クライアントとサービス間のセキュリティハンドシェイクが失敗したことを意味します。サービスはリクエスト内容ではなく、呼び出し元の「身元」を拒否しています。原因は Windows 認証の設定ミスから証明書の期限切れ、マシン間の時刻のずれまで多岐にわたります。
本記事では、このエラーの具体的な原因と解決手順を体系的に解説します。
セキュリティネゴシエーションの仕組み
修正に入る前に、WCF のセキュリティネゴシエーションの流れを理解しておきましょう。
- バインディングネゴシエーション — クライアントがメタデータまたは設定からサービスのセキュリティ要件を読み取る
- 資格情報の交換 — バインディングの
clientCredentialTypeに基づき、Windows トークン、証明書、またはユーザー名/パスワードを提示する - トークン検証 — サービスが提示された資格情報を検証する
- セキュリティコンテキスト確立 — 検証成功後、後続の呼び出し用にセキュアな会話トークンが作成される
SecurityNegotiationException はステップ 2 または 3 で失敗した場合に発生します。問題は、エラーメッセージがどのステップで、なぜ失敗したかをほとんど教えてくれないことです。
原因 1: Windows 認証の失敗 (NTLM/Kerberos)
社内ネットワークの IIS でホストされている WCF サービスを呼び出す際に最も一般的なケースです。サービスは Windows 統合認証を期待していますが、クライアントの Windows ID を検証できません。
症状
- 同一マシンでは動作するが、別マシンからは失敗する
- 特定のユーザーアカウントでのみ失敗する
- 特定のネットワークセグメントからのみ失敗する
診断
サービスの web.config でバインディング設定を確認します。
<bindings>
<wsHttpBinding>
<binding name="SecureBinding">
<security mode="Message">
<message clientCredentialType="Windows" />
</security>
</binding>
</wsHttpBinding>
</bindings>
clientCredentialType="Windows" の場合、サービスは有効な Kerberos チケットまたは NTLM トークンを期待しています。
修正方法
方法 A: クライアントがドメインアカウントで動作していることを確認する。 Windows サービスや IIS アプリケーションプールの場合、NETWORK SERVICE や LOCAL SYSTEM ではなくドメインアカウントを使用してください。
方法 B: コードで明示的に資格情報を設定する:
var client = new MyServiceClient("WSHttpBinding_IMyService");
client.ClientCredentials.Windows.ClientCredential = new NetworkCredential(
"serviceaccount",
"password",
"MYDOMAIN"
);
方法 C: Kerberos が使えない場合は NTLM にフォールバックする (クロスドメインのシナリオで一般的):
<bindings>
<wsHttpBinding>
<binding name="NtlmBinding">
<security mode="Transport">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</wsHttpBinding>
</bindings>
原因 2: Kerberos SPN の設定ミス
WCF が Kerberos 認証を使用する場合、クライアントはターゲットサービスの正しい Service Principal Name (SPN) を見つける必要があります。SPN が存在しないか、間違ったアカウントを指している場合、Kerberos チケット要求はドメインコントローラレベルで失敗します。
診断
setspn コマンドで登録済みの SPN を確認します。
setspn -L MYDOMAIN\ServiceAccount
サービスが https://api.example.com/MyService.svc でホストされている場合、以下の SPN が必要です。
HTTP/api.example.com
修正方法
正しい SPN を登録します。
setspn -S HTTP/api.example.com MYDOMAIN\AppPoolAccount
クライアント側では、エンドポイント設定で SPN の ID を明示的に指定できます。
<client>
<endpoint address="https://api.example.com/MyService.svc"
binding="wsHttpBinding"
bindingConfiguration="SecureBinding"
contract="IMyService">
<identity>
<servicePrincipalName value="HTTP/api.example.com" />
</identity>
</endpoint>
</client>
重要: SPN の重複も失敗の原因となります。setspn -X でドメイン全体の重複を確認してください。
原因 3: 証明書の不一致または期限切れ
バインディングが clientCredentialType="Certificate" を使用している場合、またはサービスがメッセージレベルのセキュリティ用に証明書を提示する場合、証明書の問題があると SecurityNegotiationException が発生します。
よくある証明書の問題
- サービス証明書の期限切れ
- クライアントがサービス証明書の CA を信頼していない
- 証明書のサブジェクト名がエンドポイントのホスト名と一致しない
- 証明書失効リスト (CRL) のチェックが失敗する
修正方法
開発環境や内部サービスの場合、クライアント側で証明書の検証をスキップできます。
<behaviors>
<endpointBehaviors>
<behavior name="IgnoreCertValidation">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None"
revocationMode="NoCheck" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
警告: 本番環境では certificateValidationMode="None" を絶対に使用しないでください。CA チェーンをクライアントマシンの信頼されたルート証明機関ストアにインストールし、期限切れの証明書を更新してください。
原因 4: セキュリティバインディングの不一致
クライアントとサーバーはセキュリティ設定が完全に一致している必要があります。クライアントのバインディングが mode="Message" を指定しているのに、サーバーが mode="Transport" を期待している場合、ネゴシエーションは即座に失敗します。
<!-- サーバーが期待する設定 -->
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
<!-- クライアントが送信する設定 -->
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
両方とも UserName 資格情報を使用していますが、セキュリティモードが異なります。Transport は TLS レベルで暗号化し、Message は SOAP エンベロープ内で暗号化します。これらは互換性がありません。
修正方法
ライブ WSDL からクライアントプロキシを再生成します。
svcutil.exe https://api.example.com/MyService.svc?wsdl /config:app.config
.NET SDK プロジェクトの場合:
dotnet-svcutil https://api.example.com/MyService.svc?wsdl
原因 5: クライアントとサーバー間の Clock Skew
セキュリティトークンにはタイムスタンプが含まれています。WCF のデフォルトの clock skew 許容範囲は 5 分です。クライアントとサーバーの時刻の差がこれを超えると、トークンが期限切れまたは未来のものと判断され、すべてのセキュリティハンドシェイクが失敗します。
修正方法
方法 A: 両方のマシンを同じ NTP ソースに同期する。
w32tm /resync /force
方法 B: バインディングで clock skew の許容範囲を拡大する:
<customBinding>
<binding name="ExtendedSkew">
<security>
<localServiceSettings maxClockSkew="00:30:00" />
<localClientSettings maxClockSkew="00:30:00" />
<secureConversationBootstrap>
<localServiceSettings maxClockSkew="00:30:00" />
<localClientSettings maxClockSkew="00:30:00" />
</secureConversationBootstrap>
</security>
<httpTransport />
</binding>
</customBinding>
診断チェックリスト
このエラーに遭遇した場合、以下の手順で診断してください。
- WCF トレースを有効にして、正確な失敗ポイントを記録する:
<system.diagnostics>
<sources>
<source name="System.ServiceModel" switchValue="Warning, ActivityTracing">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="c:\logs\wcf-trace.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
.svclogファイルを Service Trace Viewer (SvcTraceViewer.exe) で開き、赤いエラーエントリを確認する- 内部例外を確認する — Kerberos、NTLM、証明書、タイムスタンプのいずれの問題かが明らかになることが多い
BasicHttpBindingでセキュリティなしのテストを行い、エンドポイントに到達可能か確認する- ファイアウォールルールで必要なポートが許可されているか確認する (Kerberos: TCP 88、HTTPS: 443)
SOAPless による解決
WCF のセキュリティ設定は非常に複雑で、SecurityNegotiationException の多くはクライアントとサーバーのセキュリティバインディングの不一致から発生します。SOAPless はこの問題を根本から解消します。
SOAPless に WSDL を登録すると、上流の SOAP サービスへの認証を SOAPless が処理します。アプリケーションからは X-API-Key ヘッダーのみを使ったシンプルな REST JSON API を呼び出すだけです。WCF バインディング、Kerberos SPN、証明書ストアの管理は一切不要になります。SOAPless は Basic Auth、WS-Security、カスタムヘッダー認証に対応し、すべての資格情報は AES-256-GCM で暗号化して保存されます。さらに SOAP 1.1/1.2 を自動検出するため、バインディングの不一致も過去のものになります。
web.config のセキュリティモード調整に何時間も費やす代わりに、SOAPless ダッシュボードに WSDL URL を貼り付けて、数分でモダンな REST インターフェース経由の呼び出しを始めましょう。