在我的MVC站点上,如果我检测到正在使用ADFS帐户,我会重定向到ADFS登录页面。用户输入ADFS凭据后,ADFS站点会将WsFederationMessage
发回我的站点。如何验证作为此WsFederationMessage
在AuthenticationHandler
中间件类的内部,我有以下调用ValidateToken
方法的相关代码:
IFormCollection form = await Request.ReadFormAsync();
WsFederationMessage wsFederationMessage = new WsFederationMessage(form);
if (!wsFederationMessage.IsSignInMessage)
{
Request.Body.Seek(0, SeekOrigin.Begin);
return null;
}
var token = wsFederationMessage.GetToken();
if (wsFederationMessage.Wresult != null && Options.SecurityTokenHandlers.CanReadToken(token))
{
SecurityToken validatedToken;
ClaimsPrincipal principal = Options.SecurityTokenHandlers.ValidateToken(token, Options.TokenValidationParameters, out validatedToken);
...
}
当我尝试拨打ValidateToken
时出现此错误:
描述:执行期间发生了未处理的异常 当前的网络请求。请查看堆栈跟踪了解更多信息 有关错误的信息以及它在代码中的起源。
异常详细信息: System.IdentityModel.SignatureVerificationFailedException:ID4037:The 验证签名无法解决的密钥 以下安全密钥标识符' SecurityKeyIdentifier(
IsReadOnly = False,Count = 1,Clause [0] = X509RawDataKeyIdentifierClause(RawData = [由作者删除]。确保 SecurityTokenResolver填充了所需的密钥。
正在搜索分辨率,我找到this article,因此我使用this site's OpenSSL-based decoder解码了上面代码中X509Certificate
字符串对象中显示的token
,因为它是PEM - 在返回的<X509Certificate></X509Certificate>
字符串的token
XAML标记内编码。事实上,正如决议文章所述,这是签署证书。所以我继续使用ADFS服务器,将签名证书导出为公共证书,并将其安装在我的网站Trusted Root Certificate Authorities
上。该链接还提到我必须:
将证书导入RP Trust的签名选项卡
因此,我将签名证书添加到我的ADFS服务器上的信赖方信任的“签名”选项卡中,其中我对我的计算机的标识符有信任规则。毕竟,它仍然没有奏效。虽然有一点背景知识,我的网站在我的机器上通过IIS本地运行,我已经更改了主机文件设置,使其指向https://adfs-example.local/
。我的ADFS服务器目前是VPN连接到我的站点,所以我说的是,如果ADFS服务器本身需要直接从此URI请求某些内容,它将无法正确解析https://adfs-example.local/
的标识符,但是,一旦浏览器重定向到我的网站的登录页面并呈现ADFS令牌,事情仍然显而易见。
抨击我的上帝更多地将头靠在墙上,我尝试添加自己的IssuerSigningKeyResolver
:
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (token, securityToken, keyIdentifier, validationParameters) =>
{
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.Find(X509FindType.FindByThumbprint, "<My Certificate's Thumbprint>", true)[0];
store.Close();
var provider = (RSACryptoServiceProvider)cert.PublicKey.Key;
return new RsaSecurityKey(provider);
}
};
现在我有这种错误之美,并且不知道如何处理它:
IDX10213:必须签署SecurityTokens。 SecurityToken:&#39; {0}&#39;。
描述:执行期间发生了未处理的异常 当前的网络请求。请查看堆栈跟踪了解更多信息 有关错误的信息以及它在代码中的起源。
异常详细信息: System.IdentityModel.Tokens.SecurityTokenValidationException: IDX10213:必须签署SecurityTokens。 SecurityToken:&#39; {0}&#39;。
来源错误:
第61行:第62行:var validatedToken = (SecurityToken)空;第63行:var principal = Options.SecurityTokenHandlers.ValidateToken(令牌, Options.TokenValidationParameters,out validatedToken);第64行:
var claimsIdentity = principal.Identity as ClaimsIdentity;第65行:
var ticket = new AuthenticationTicket(claimsIdentity,null);
处理程序被调用两次。在第一次通话时,这似乎成功了。似乎第一个令牌已经签署。在第二次调用时,它失败了。似乎第二个令牌没有签名。为什么我的部分安全令牌没有签署?我该如何进一步调试?任何人都必须处理这样的事情吗?
现在我别无选择,只能检查来源,所以我拉了AzureAD的整个主干(也称为Wilson),我正在查看代码。它在SAML安全令牌处理程序的这一行失败:
if (samlToken.Assertion.SigningToken == null && validationParameters.RequireSignedTokens)
{
throw new SecurityTokenValidationException(ErrorMessages.IDX10213);
}
我不明白。这意味着签名令牌为空。为什么签名令牌为空?
编辑:再次检查ADFS服务器,我认为无论是谁设置它都忘记将私钥作为&#34;令牌签名&#34;的一部分。和#34;令牌解密&#34;证书是AD FS的一部分 - &gt;服务 - &gt; ADFS管理单元的“证书”选项卡。但奇怪的是,通过与设置它的人交谈,显然它需要服务证书并吐出其他两个用于令牌签名和解密...但没有他们的私钥?
修改:根据this article,这两个&#34;令牌签名&#34;和#34;令牌解密&#34;证书应该是自动生成的,只要它们的私钥存储在Active Directory中
使用自签名证书进行令牌签名时 解密时,私钥存储在Active Directory中 以下容器:
CN = ADFS,CN = Microsoft,CN = Program Data,DC = domain,DC = com
因此,为ADFS安装安装私钥 进入此位置,您必须是域管理员才能安装ADFS或拥有 分配给此容器的适当权限。
答案 0 :(得分:5)
最后,我放弃了AzureAD Nuget软件包,它没有任何理由引起头疼。我采取直接的方法。现在我只是简单地要求我的AD FS服务器验证用户凭据。以下是代码(只需确保安装了Windows Identity Foundation SDK,并添加对Microsoft.IdentityModel.dll
,System.IdentityModel.dll
和System.ServiceModel.dll
的引用:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Xml;
using Microsoft.IdentityModel.Protocols.WSTrust;
using Microsoft.IdentityModel.Protocols.WSTrust.Bindings;
namespace ADFSFederationToken
{
class Program
{
static string _relyingPartyIdentifier = "https://yourapplication.local/"; // Must be whatever you specified on your AD FS server as the relying party address.
static string _adfsServerAddress = "https://adfs.example.local/"; // Your ADFS server's address.
static string _username = "username@domain.local"; // A username to your ADFS server.
static string _password = "password"; // A password to your ADFS server.
static string _signingCertificateThumbprint = "1337..."; // Put the public ADFS Token Signing Certificate's thumbprint here and be sure to add it to your application's trusted certificates in the Certificates snap-in of MMC.
static string _signingCertificateCommonName = "ADFS Signing - adfs.example.local"; // Put the common name of the ADFS Token Signing Certificate here.
static void Main(string[] args)
{
Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory factory = null;
try
{
_relyingPartyIdentifier = _relyingPartyIdentifier.EndsWith("/") ? _relyingPartyIdentifier : _relyingPartyIdentifier + "/";
_adfsServerAddress = _adfsServerAddress.EndsWith("/") ? _adfsServerAddress : _adfsServerAddress + "/";
factory = new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress(_adfsServerAddress + "adfs/services/trust/13/usernamemixed"));
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = _username;
factory.Credentials.UserName.Password = _password;
var rst = new Microsoft.IdentityModel.Protocols.WSTrust.RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(_relyingPartyIdentifier),
KeyType = WSTrust13Constants.KeyTypes.Bearer
};
var channel = factory.CreateChannel();
var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
var handler = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
var tokenString = genericToken.TokenXml.OuterXml;
var samlToken = handler.ReadToken(new XmlTextReader(new StringReader(tokenString)));
ValidateSamlToken(samlToken);
}
finally
{
if (factory != null)
{
try
{
factory.Close();
}
catch (CommunicationObjectFaultedException)
{
factory.Abort();
}
}
}
}
public static ClaimsIdentity ValidateSamlToken(SecurityToken securityToken)
{
var configuration = new SecurityTokenHandlerConfiguration();
configuration.AudienceRestriction.AudienceMode = AudienceUriMode.Always;
configuration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(_relyingPartyIdentifier));
configuration.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
configuration.RevocationMode = X509RevocationMode.Online;
configuration.CertificateValidator = X509CertificateValidator.ChainTrust;
var registry = new ConfigurationBasedIssuerNameRegistry();
registry.AddTrustedIssuer(_signingCertificateThumbprint, _signingCertificateCommonName);
configuration.IssuerNameRegistry = registry;
var handler = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(configuration);
var identity = handler.ValidateToken(securityToken).First();
return identity;
}
}
}
编辑:如果我想从AzureAD NuGet包开始工作并继续重定向并使用他们的表单发布请求解析器,我仍然可以使用上面的代码执行此操作。我仍然可以读取XAML标记字符串并解析为有效的SecurityToken
对象,如下所示:
var token = wsFederationMessage.GetToken();
var samlToken = handler.ReadToken(new XmlTextReader(new StringReader(token)));
ValidateSamlToken(samlToken);