如何验证此ADFS令牌?

时间:2015-10-20 12:34:29

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

在我的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: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或拥有   分配给此容器的适当权限。

1 个答案:

答案 0 :(得分:5)

最后,我放弃了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);