"Add SAML" 在 AD FS 2.0 管理器上被禁用

"Add SAML" is disabled on AD FS 2.0 manager

当我在 AD FS 管理器上手动添加依赖方时,我能够更新 "Endpoints" 并设置 SAML 注销,但是当我将提供 URL 的 RP 添加到 federatedmetadata.xml 文件并且创建 RP 后,AD FS 管理器不允许我将 SAML 注销 link 添加到我新创建的 RP。

我想如果我必须只使用 URL 到 XML 配置我的 RP,那么添加 SAML 注销的唯一方法是将它包含在 federatedmetadata.xml 中,但我不知道如何配置包含单点退出 link 的 XML ,最让我担心的是我无法做到这一点,因为如果我更改我的 XML 手动它将变得不可用,因为一旦您更改 XML.

AD FS 管理器就会开始抛出异常

当 AD FS 管理器完全禁用此类选项时,如何在 AD FS 2.0 上为我的 RP 添加 SAML 注销?

当您手动添加 RP 信任时,是否勾选了 "Enable support for SAML" 以及 "Enable support for "WS-Fed" 的复选框?

当您这样做时,两个 "Add" 框都会启用。

为了允许管理端点,同时在 AD FS 上设置 RP,URL 指向 FederationMetadata.xml,我必须创建自己的 FederationMetadata.xml,其中包含所有必要的端点、签名、加密等。在此之前,我使用了 FedAuthUtil,但它并没有完成所有工作。

非常感谢 Evan Larsen 的 blog post,他完成了几乎所有脏活,尽管必须应用一些更改。

public class FederationMetadataHandler : IHttpHandler
{
    private object criticalSection = new object();
    private volatile EntityDescriptor entity;
    private string relyingPartyUrl = WebConfigurationManager.AppSettings[""];
    private string relyingPartySignOutUrl = WebConfigurationManager.AppSettings[""];
    private string signingCertificateThumbprint = WebConfigurationManager.AppSettings[""];
    private string encryptionCertificateThumbprint = WebConfigurationManager.AppSettings[""];

    public FederationMetadataHandler() { }

    // This tells the website that this service is re-usable
    // so it does not need to run the code everytime. It will keep an instances of this
    // class and just return the FederationMetadata.xml file once its been processed once
    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        EnsureInitialized();
        context.Response.Clear();
        context.Response.ContentType = "text/xml";
        context.Response.ContentEncoding = System.Text.Encoding.UTF8;

        MetadataSerializer serializer = new MetadataSerializer();
        serializer.WriteMetadata(context.Response.OutputStream, entity);
    }

    private void EnsureInitialized()
    {
        if (entity == null)
        {
            lock (criticalSection)
            {
                if (entity == null)
                {
                    HttpContext context = HttpContext.Current;
                    Uri baseUri = new Uri(new Uri(context.Request.Url.AbsoluteUri), context.Request.ApplicationPath);

                    ApplicationServiceDescriptor sts = new ApplicationServiceDescriptor();

                    SingleSignOnDescriptor sso = new SingleSignOnDescriptor();

                    ServiceProviderSingleSignOnDescriptor spsso = new ServiceProviderSingleSignOnDescriptor();

                    FillRequestedClaimTypes(sts.ClaimTypesRequested);
                    FillEndPoints(sts);
                    FillSupportedProtocols(sts);
                    FillSigningKey(sts);
                    FillEncryptionKey(sts);
                    FillSingleSignOutEndPoints(spsso);

                    EntityId id = new EntityId(this.relyingPartyUrl);
                    entity = new EntityDescriptor(id);
                    entity.SigningCredentials = GetSigningCredentials();

                    entity.RoleDescriptors.Add(sts);
                    entity.RoleDescriptors.Add(spsso);
                }
            }
        }
    }

    private void FillRequestedClaimTypes(ICollection<DisplayClaim> claimTypes)
    {
        // possibly make this read from a config file in the future..
        //ClaimsProvider.GetDisplayClaims().ForEach(dc => claimTypes.Add(dc));

        DisplayClaim name = new DisplayClaim(ClaimTypes.Name);
        claimTypes.Add(name);
    }

    private void FillEndPoints(ApplicationServiceDescriptor sts)
    {
        EndpointAddress activeEndpoint = new EndpointAddress(this.relyingPartyUrl);
        sts.TargetScopes.Add(activeEndpoint);
        sts.PassiveRequestorEndpoints.Add(activeEndpoint);
    }

    private void FillSupportedProtocols(ApplicationServiceDescriptor sts)
    {
        sts.ProtocolsSupported.Add(new Uri(WSFederationConstants.Namespace));
    }

    private void FillSigningKey(ApplicationServiceDescriptor sts)
    {
        KeyDescriptor key = GetKeyDescriptor(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, signingCertificateThumbprint, KeyType.Signing);
        sts.Keys.Add(key);
    }

    private void FillEncryptionKey(ApplicationServiceDescriptor sts)
    {
        KeyDescriptor key = GetKeyDescriptor(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, encryptionCertificateThumbprint, KeyType.Encryption);
        sts.Keys.Add(key);
    }

    private void FillSingleSignOutEndPoints(ServiceProviderSingleSignOnDescriptor spsso)
    {
        spsso.ProtocolsSupported.Add(new Uri("urn:oasis:names:tc:SAML:1.1:protocol"));
        spsso.ProtocolsSupported.Add(new Uri("urn:oasis:names:tc:SAML:2.0:protocol"));
        spsso.AssertionConsumerService.Add(0, new IndexedProtocolEndpoint(0, new Uri("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), new Uri(relyingPartyUrl)));
        spsso.SingleLogoutServices.Add(new ProtocolEndpoint(new Uri("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"), new Uri(relyingPartySignOutUrl)));
    }

    private Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials GetSigningCredentials()
    {
        X509Certificate2 certificate = GetCertificate(StoreName.My, StoreLocation.LocalMachine, X509FindType.FindByThumbprint, encryptionCertificateThumbprint);
        if (certificate == null) throw new Exception("Signing certificate is not found");
        return new Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials(certificate);
    }

    private KeyDescriptor GetKeyDescriptor(StoreName storeName, StoreLocation storeLocation, X509FindType findByType, object findByValue, KeyType keyType)
    {
        X509Certificate2 certificate = GetCertificate(storeName, storeLocation, findByType, findByValue);
        if (certificate == null) throw new Exception(string.Format(@"Certificate for type \""{0}\"" is not found", findByType));
        X509RawDataKeyIdentifierClause clause = new X509SecurityToken(certificate).CreateKeyIdentifierClause<X509RawDataKeyIdentifierClause>();
        KeyDescriptor key = new KeyDescriptor(new SecurityKeyIdentifier(clause));
        key.Use = keyType;
        return key;
    }

    private X509Certificate2 GetCertificate(StoreName storeName, StoreLocation storeLocation, X509FindType findBy, object findValue)
    {
        X509Certificate2 certificate = null;
        X509Store store = new X509Store(storeName, storeLocation);
        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
        X509Certificate2Collection matchedCertificates = store.Certificates.Find(findBy, findValue, false);
        if (matchedCertificates.Count > 0) certificate = matchedCertificates[0];
        return certificate;
    }
}