【问题标题】:How can I validate this ADFS token?如何验证此 ADFS 令牌?
【发布时间】:2016-01-19 02:55:49
【问题描述】:

在我的 MVC 站点上,如果我检测到正在使用 ADFS 帐户,我会重定向到 ADFS 登录页面。用户输入其 ADFS 凭据后,ADFS 站点会将WsFederationMessage 发回我的站点。如何验证作为 WsFederationMessage 的一部分呈现给我的网站的 ADFS 令牌?

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: 验证签名所需的密钥无法从 以下安全密钥标识符 'SecurityKeyIdentifier (
IsReadOnly = False,计数 = 1,子句 [0] = X509RawDataKeyIdentifierClause(RawData = [作者删除]。确保 SecurityTokenResolver 填充了所需的密钥。

搜索分辨率时,我找到了this article,因此我使用this site's OpenSSL-based decoder 解码了上面代码中token 字符串对象中出现的X509Certificate,因为它是在<X509Certificate></X509Certificate> XAML 中进行PEM 编码的返回的token 字符串的标记。事实上,它是签名证书,正如决议文章所说的那样。所以我继续使用我的 ADFS 服务器,将签名证书导出为公共证书并将其安装在我网站的 Trusted Root Certificate Authorities 上。该链接还提到我必须:

将证书导入 RP 信任的签名选项卡

因此,我将签名证书添加到我的 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);
    }
};

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

IDX10213:必须对 SecurityTokens 进行签名。安全令牌:“{0}”。

描述:执行过程中发生了未处理的异常 当前的网络请求。请查看堆栈跟踪以获取更多信息 有关错误的信息以及它在代码中的来源。

异常详情: System.IdentityModel.Tokens.SecurityTokenValidationException: IDX10213:必须对 SecurityTokens 进行签名。安全令牌:“{0}”。

来源错误:

第 61 行:第 62 行:var validToken = (安全令牌)空;第 63 行:var principal = Options.SecurityTokenHandlers.ValidateToken(令牌, Options.TokenValidationParameters, outvalidatedToken);第 64 行:
var claimIdentity = principal.Identity 作为 ClaimsIdentity;第 65 行:
var ticket = new AuthenticationTicket(claimsIdentity, null);

处理程序被调用两次。在第一次通话中,这似乎成功了。似乎第一个令牌已签名。在第二次调用时,它失败了。似乎第二个令牌未签名。为什么我的一些安全令牌没有签名?我该如何进一步调试呢?任何人都必须处理这样的事情吗?

现在我别无选择,只能检查源代码,所以我拉出了AzureAD(也称为Wilson)的整个主干,我正在检查代码。它在 SAML 安全令牌处理程序的这一行失败:

if (samlToken.Assertion.SigningToken == null && validationParameters.RequireSignedTokens)
{
    throw new SecurityTokenValidationException(ErrorMessages.IDX10213);
}

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

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

编辑:根据this article,这两个“令牌签名”和“令牌解密”证书应该是有效的,因为它们是自动生成的,只是存储了它们的私钥在活动目录中:

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

CN=ADFS,CN=Microsoft,CN=Program Data,DC=domain,DC=com

因此,为 ADFS 安装安装私钥 进入此位置,您必须是域管理员才能安装 ADFS 或拥有 分配给此容器的适当权限。

【问题讨论】:

    标签: c# asp.net asp.net-mvc ssl adfs


    【解决方案1】:

    最后,我放弃了 AzureAD Nuget 包,它无缘无故地引起了头痛。我采取了直接的方法。现在我只是要求我的 AD FS 服务器验证用户凭据。这是代码(请确保安装了Windows Identity Foundation SDK,并添加对Microsoft.IdentityModel.dllSystem.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;
            }
        }
    }
    

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

    var token = wsFederationMessage.GetToken();
    var samlToken = handler.ReadToken(new XmlTextReader(new StringReader(token)));
    ValidateSamlToken(samlToken);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-12
      • 1970-01-01
      • 2017-08-25
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 2019-12-01
      • 1970-01-01
      相关资源
      最近更新 更多