如何验证此ADFS令牌?

Ale*_*dru 6 c# asp.net asp.net-mvc ssl adfs

在我的MVC站点上,如果我检测到正在使用ADFS帐户,我会重定向到ADFS登录页面.用户输入其ADFS凭据后,ADFS站点将WsFederationMessage回发到我的站点.如何验证作为其中一部分呈现给我的站点的ADFS令牌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);
    ...
}
Run Code Online (Sandbox Code Playgroud)

我试图打电话时收到此错误ValidateToken:

描述:执行当前Web请求期间发生未处理的异常.请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息.

异常详细信息:System.IdentityModel.SignatureVerificationFailedException:ID4037:无法从以下安全密钥标识符'SecurityKeyIdentifier解析验证签名所需的密钥(
IsReadOnly = False,Count = 1,Clause [0] = X509RawDataKeyIdentifierClause(RawData = [已删除]作者].确保使用所需的密钥填充SecurityTokenResolver.

搜索一个分辨率,我找到了这篇文章,所以我使用这个网站的基于OpenSSL的解码器解码了上面代码中X509Certificatetoken字符串对象中的呈现,因为它是在返回字符串的XAML标记内进行PEM编码.事实上,正如决议文章所述,这是签署证书.所以我继续使用我的ADFS服务器,将签名证书导出为公共证书并将其安装在我的网站上.该链接还提到我必须:<X509Certificate></X509Certificate>tokenTrusted Root Certificate Authorities

将证书导入RP Trust的"签名"选项卡

所以我将签名证书添加到我的ADFS服务器上的信赖方信任的签名选项卡中,其中我有一个机器标识符的信任规则.毕竟,它仍然无法正常工作.虽然有一点背景知识,我的网站在我的机器上通过IIS本地运行,我已经更改了主机文件设置以使其指向https://adfs-example.local/.我的ADFS服务器目前是VPN连接到我的站点,所以我所说的是ADFS服务器本身永远无法正确解析一个标识符,https://adfs-example.local/如果它需要直接从这个URI请求一些东西,但事情一旦显然仍然有效浏览器重定向到我网站的登录页面并显示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);
    }
};
Run Code Online (Sandbox Code Playgroud)

现在我有一个错误的美丽,并不知道如何处理它:

IDX10213:必须签署SecurityTokens.SecurityToken:'{0}'.

描述:执行当前Web请求期间发生未处理的异常.请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息.

异常详细信息:System.IdentityModel.Tokens.SecurityTokenValidationException:IDX10213:SecurityTokens必须已签名.SecurityToken:'{0}'.

来源错误:

第61行:第62行:var validatedToken =(SecurityToken)null; 第63行:var principal = Options.SecurityTokenHandlers.ValidateToken(token,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);
}
Run Code Online (Sandbox Code Playgroud)

我不明白.这意味着签名令牌为空.为什么签名令牌为空?

编辑:再次检查ADFS服务器,我想无论谁设置它都忘记包含私钥作为"令牌签名"和"令牌解密"证书的一部分,这些证书是AD FS - >服务 - >证书选项卡的一部分ADFS管理单元.但奇怪的是,通过与设置它的人交谈,显然它需要服务证书并吐出其他两个用于令牌签名和解密......但没有他们的私钥?

编辑:根据这篇文章,这两个"令牌签名"和"令牌解密"证书应该是有效的,因为它们是自动生成的,只是他们的私钥存储在Active Directory中:

使用自签名证书进行令牌签名和解密时,私钥存储在以下容器的Active Directory中:

CN = ADFS,CN = Microsoft,CN =程序数据,DC =域,DC = com

因此,要将ADFS安装到此位置安装私钥,您必须是域管理员才能安装ADFS或将相应的权限分配给此容器.

Ale*_*dru 5

最后,我放弃了AzureAD Nuget软件包,它没有引起任何原因,但没有任何理由.我采取直接的方法.现在我只是简单地要求我的AD FS服务器验证用户凭据.下面是代码(只是一定要拥有的Windows标识基础SDK安装,并添加引用Microsoft.IdentityModel.dll,System.IdentityModel.dllSystem.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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:如果我想从AzureAD NuGet包工作并继续重定向并使用他们的表单发布请求解析器,我仍然可以使用上面的代码.我仍然可以读取XAML标记字符串并解析为有效SecurityToken对象,如下所示:

var token = wsFederationMessage.GetToken();
var samlToken = handler.ReadToken(new XmlTextReader(new StringReader(token)));
ValidateSamlToken(samlToken);
Run Code Online (Sandbox Code Playgroud)