更换依赖方应用池用户后WSFederation报密钥状态无效异常
我接手了一台运行.NET 4.7.2的STS服务器,它使用System.IdentityModel WSFederation基于主体颁发令牌,并通过SessionAuthenticationModule将令牌保存为Cookie。STS与依赖方均部署在Server 2016系统的IIS 10上,原本运行正常,但在更换依赖方应用池的运行身份后,出现如下错误:
[CryptographicException: Key not valid for use in specified state. ] System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope) +470 System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +48 [InvalidOperationException: ID1073: A CryptographicException occurred when attempting to decrypt the cookie using the ProtectedData API (see inner exception for details). If you are using IIS 7.5, this could be due to the loadUserProfile setting on the Application Pool being set to false. ] System.IdentityModel.ProtectedDataCookieTransform.Decode(Byte[] encoded) +358 System.IdentityModel.Tokens.SessionSecurityTokenHandler.ApplyTransforms(Byte[] cookie, Boolean outbound) +191 System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver) +824 System.IdentityModel.Tokens.SessionSecurityTokenHandler.ReadToken(Byte[] token, SecurityTokenResolver tokenResolver) +86 System.IdentityModel.Services.SessionAuthenticationModule.ReadSessionTokenFromCookie(Byte[] sessionCookie) +567 System.IdentityModel.Services.SessionAuthenticationModule.TryReadSessionTokenFromCookie(SessionSecurityToken& sessionToken) +306 System.IdentityModel.Services.SessionAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs eventArgs) +158 System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +223 System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +220 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +94
看起来依赖方无法解密Cookie,但为何Cookie解密会与应用池运行身份绑定?我已将新身份设为服务器管理员以排除权限问题,是否要求STS与所有依赖方使用相同运行用户?
原因分析与解决方案
解密与应用池身份绑定的核心原因
默认情况下,ProtectedDataCookieTransform使用DataProtectionScope.CurrentUser加密/解密Cookie。这个Windows DPAPI机制依赖于当前用户配置文件中的专属加密密钥——每个用户的密钥完全独立,用用户A加密的数据,只有用户A能解密。你更换了依赖方应用池的运行身份,相当于用新用户的密钥去解密旧用户加密的Cookie,密钥不匹配就会抛出Key not valid for use in specified state异常。
是否要求STS与依赖方用相同运行用户?
不需要强制使用同一用户,但必须保证加密/解密的密钥一致。以下是几种可行方案:
方案1:改用机器级DPAPI保护范围
修改STS和所有依赖方的配置,让Cookie加密使用DataProtectionScope.LocalMachine(机器级密钥)。同一台机器上的所有应用,不管应用池身份是谁,都能使用机器密钥解密。
- 首先自定义
ProtectedDataCookieTransform子类,指定机器级范围:
public class MachineProtectedDataCookieTransform : ProtectedDataCookieTransform { public MachineProtectedDataCookieTransform() : base(DataProtectionScope.LocalMachine) { } }
- 然后在web.config中替换默认的转换类型:
<system.identityModel> <identityConfiguration> <securityTokenHandlers> <add type="System.IdentityModel.Services.Tokens.SessionSecurityTokenHandler, System.IdentityModel.Services"> <sessionTokenRequirement> <cookieHandler mode="Default"> <protection>All</protection> <transforms> <clear /> <add type="YourNamespace.MachineProtectedDataCookieTransform, YourAssembly" /> </transforms> </cookieHandler> </sessionTokenRequirement> </add> </securityTokenHandlers> </identityConfiguration> </system.identityModel>
方案2:使用共享对称密钥加密
放弃DPAPI,改用统一的共享对称密钥来加密Cookie。STS和所有依赖方必须使用完全相同的密钥。
- 在web.config中配置加密转换和共享密钥:
<system.identityModel> <identityConfiguration> <securityTokenHandlers> <add type="System.IdentityModel.Services.Tokens.SessionSecurityTokenHandler, System.IdentityModel.Services"> <sessionTokenRequirement> <cookieHandler mode="Default"> <protection>All</protection> <transforms> <clear /> <add type="System.IdentityModel.Tokens.DeflateCookieTransform, System.IdentityModel" /> <add type="System.IdentityModel.Tokens.SignedXmlCookieTransform, System.IdentityModel" /> <add type="System.IdentityModel.Tokens.EncryptedXmlCookieTransform, System.IdentityModel" /> </transforms> </cookieHandler> </sessionTokenRequirement> <securityTokenSerializer> <securityTokenHandlerConfiguration> <serviceTokenResolver> <add key="SharedEncryptionKey" value="你的Base64编码共享密钥" /> </serviceTokenResolver> </securityTokenHandlerConfiguration> </securityTokenSerializer> </add> </securityTokenHandlers> </identityConfiguration> </system.identityModel>
注意:密钥要妥善保管,避免泄露。
方案3:确保应用池加载用户配置文件
错误提示中提到的loadUserProfile设置,如果新应用池身份没有加载用户配置文件,DPAPI无法访问该用户的加密密钥。可以在IIS应用池高级设置中,将Load User Profile设为True。但这个方案只适用于STS和依赖方使用同一用户身份的场景,身份不同的话还是需要用前面两种方案。
内容的提问来源于stack exchange,提问作者hada

