Claims系列 - ID4036错误(The key needed to decrypt the encrypted security token could not be resolved from the following security key identifier)

错误现象

 

错误原因

a) 在SecurityTokenService.GetScope()方法中,设置了Scope.EncryptingCredentials属性为某个X509证书(公钥);

b) Relying Party应用程序中没有设置对应的证书(包含私钥)。

 

原因分析

从出错页面的规模信息可知, ID4036是由ID1044错误引发的。

导致ID1044异常的代码为Microsoft.IdentityModel.Web.TokenReceiver.ReadToken(String tokenXml, XmlDictionaryReaderQuotas readerQuotas)。

通过.Net Relector查看TokenReceiver.ReadToken()的内部实现如下:

 1 public SecurityToken ReadToken(string tokenXml, XmlDictionaryReaderQuotas readerQuotas)
 2 {
 3  //此处无关代码被省略
 4  catch (EncryptedTokenDecryptionFailedException exception)
 5     {
 6         string str3;
 7         if (this._serviceConfiguration.ServiceCertificate == null)
 8         {
 9             str3 = SR.GetString("NoCert"new object[0]);
10         }
11         else
12         {
13             str3 = "[Thumbprint] " + this._serviceConfiguration.ServiceCertificate.Thumbprint;
14         }
15         throw DiagnosticUtil.ExceptionUtil.ThrowHelperError(new SecurityTokenException(SR.GetString("ID1044"new object[] { str3 }), exception));
16     }
17     return token2;
18 }

显而易见,ID1044异常是由于this._serviceConfiguration.ServiceCertificate == null引起。

 

再查看TokenReceiver类,可知_serviceConfiguration是其私有字段,拍被设置的地方是在构造函数之中,并且是从外部传过来的.

1 public TokenReceiver(ServiceConfiguration serviceConfiguration)
2 {
3     if (serviceConfiguration == null)
4     {
5         throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("serviceConfiguration");
6     }
7     this._serviceConfiguration = serviceConfiguration;
8 }

那么此构造函数在什么地方调用的呢?从调用堆栈可知,推测可能是WSFederationAuthenticationModule.SignInWithResponseMessage(HttpRequest request).

打开.Net Reflector一看,果不其然:

 1 private void SignInWithResponseMessage(HttpRequest request)
 2 {
 3 //省略前面的无关代码
 4 
 5     if (!e.Cancel)
 6     {
 7         TokenReceiver receiver = new TokenReceiver(base.ServiceConfiguration);
 8         IClaimsPrincipal claimsPrincipal = receiver.AuthenticateToken(e.SecurityToken, true, HttpContext.Current.Request.RawUrl);
 9 
10 //省略后面的无关代码
11 
12     }
13 }

 传入的参数值为base.ServiceConfiguration,即HttpModuleBase.ServiceConfiguration;其定义如下:

 1 public ServiceConfiguration ServiceConfiguration
 2 {
 3     get
 4     {
 5         return this._serviceConfiguration;
 6     }
 7     set
 8     {
 9         if (value == null)
10         {
11             throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("value");
12         }
13         this._serviceConfiguration = value;
14     }
15 }

由于ServiceConfiguration是可读写属性,因此可能从外部和内部设置值。

先看外部有没有可能。从调用堆栈可知,其调用都为WSFederationAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs args):

View Code
 1 protected virtual void OnAuthenticateRequest(object sender, EventArgs args)
 2 {
 3     HttpRequest request = HttpContext.Current.Request;
 4     if (this.CanReadSignInResponse(request))
 5     {
 6         try
 7         {
 8             this.SignInWithResponseMessage(request);
 9         }
10         catch (Exception exception)
11         {
12             if (DiagnosticUtil.IsFatal(exception))
13             {
14                 throw;
15             }
16             if (DiagnosticUtil.TraceUtil.ShouldTrace(TraceEventType.Warning))
17             {
18                 DiagnosticUtil.TraceUtil.TraceString(TraceEventType.Warning, SR.GetString("ID8020"new object[] { exception }), new object[0]);
19             }
20             ErrorEventArgs args2 = new ErrorEventArgs(exception);
21             this.OnSignInError(args2);
22             if (!args2.Cancel)
23             {
24                 throw;
25             }
26         }
27     }
28 }

从其实现看,并没有设置ServiceConfiguration值的地方;回过头再看HttpModuleBase,可知设置的地方是Init()函数:

1 public void Init(HttpApplication context)
2 {
3     this._serviceConfiguration = FederatedAuthentication.ServiceConfiguration;
4     this.InitializeModule(context);
5 }

并且直接引用了FederatedAuthentication.ServiceConfiguration属性。再查看这个属性的定义:

 

 1 public static ServiceConfiguration ServiceConfiguration
 2 {
 3     get
 4     {
 5         lock (_serviceConfigurationLock)
 6         {
 7             if (_serviceConfiguration == null)
 8             {
 9                 _serviceConfiguration = new ServiceConfiguration();
10                 ServiceConfigurationCreatedEventArgs e = new ServiceConfigurationCreatedEventArgs(_serviceConfiguration);
11                 EventHandler<ServiceConfigurationCreatedEventArgs> serviceConfigurationCreated = ServiceConfigurationCreated;
12                 if (serviceConfigurationCreated != null)
13                 {
14                     serviceConfigurationCreated(null, e);
15                 }
16                 _serviceConfiguration = e.ServiceConfiguration;
17                 if (!_serviceConfiguration.IsInitialized)
18                 {
19                     _serviceConfiguration.Initialize();
20                 }
21             }
22             return _serviceConfiguration;
23         }
24     }
25 }

从代码中可得到两点信息:1)_serviceConfiguration是在第一次请求时new出来的; 2)在第一次请求时会触发ServiceConfigurationCreated事件,并可访问到

_serviceConfiguration。

再看ServiceConfiguration的构造函数:

View Code
 1 public ServiceConfiguration()
 2 {
 3     this._certificateValidationMode = DefaultCertificateValidationMode;
 4     this._claimsAuthenticationManager = new ClaimsAuthenticationManager();
 5     this._claimsAuthorizationManager = new ClaimsAuthorizationManager();
 6     this._exceptionMapper = new ExceptionMapper();
 7     this._revocationMode = DefaultRevocationMode;
 8     this._serviceName = DefaultServiceName;
 9     this._serviceMaxClockSkew = DefaultMaxClockSkew;
10     this._trustedStoreLocation = DefaultTrustedStoreLocation;
11     MicrosoftIdentityModelSection current = MicrosoftIdentityModelSection.Current;
12     ServiceElement element = (current != null) ? current.ServiceElements.GetElement(DefaultServiceName) : null;
13     this.LoadConfiguration(element);
14 }

注意到最后调用了LoadConfiguration进行初始化,再看其内部实现:

 1 protected void LoadConfiguration(ServiceElement element)
 2 {
 3     if (element != null)
 4     {
 5 
 6 //省略前面无关代码
 7         if ((this._serviceCertificate == null) && element.ServiceCertificate.IsConfigured)
 8         {
 9             this._serviceCertificate = GetServiceCertificate(element);
10         }
11 //省略后面无关代码
12     }
13     this._securityTokenHandlerCollectionManager = this.LoadHandlers(element);
14 }

再看GetServiceCertificate()

 

 1 private static X509Certificate2 GetServiceCertificate(ServiceElement element)
 2 {
 3     X509Certificate2 certificate2;
 4     try
 5     {
 6         X509Certificate2 certificate = element.ServiceCertificate.GetCertificate();
 7         if (certificate != null)
 8         {
 9             X509Util.EnsureAndGetPrivateRSAKey(certificate);
10         }
11         certificate2 = certificate;
12     }
13     catch (ArgumentException exception)
14     {
15         throw DiagnosticUtil.ExceptionUtil.ThrowHelperConfigurationError(element, "serviceCertificate", exception);
16     }
17     return certificate2;
18 }

至此, 终于知道X509证书默认是从ServiceElement即配置文件中的<microsoft.identitymodel><service><servicecertificate>节点。由此我们可得到如下两种解决方案:

解决方案

1 设置配置文件中的<microsoft.identitymodel><service><servicecertificate>节点

  a)找开Relying Party应用程序的配置文件;

  b)设置X509证书如下:

 

2 在FederatedAuthentication.ServiceConfigurationCreated事件处理函数中设置

   a) 在Relying Party工程中添加Global.asax文件(如果不存在的话);

   b) 添加Application_Start事件处理函数

 

posted @ 2012-07-26 11:58  Rickey Hu  阅读(1408)  评论(1编辑  收藏  举报