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()函数:
2 {
3 this._serviceConfiguration = FederatedAuthentication.ServiceConfiguration;
4 this.InitializeModule(context);
5 }
并且直接引用了FederatedAuthentication.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的构造函数:

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进行初始化,再看其内部实现:
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()
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事件处理函数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步