You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVC 5应用中Sustainsys.Saml2多租户实现及相关问题咨询

多租户下Sustainsys.Saml2配置与问题解决

一、添加多租户身份提供者并在SignIn时指定IDP

1. 配置多个身份提供者

在web.config的<identityProviders>节点下添加多个<add>元素,每个对应一个租户的身份提供者(IDP),注意每个IDP的entityId需唯一:

<sustainsys.saml2 entityId="https://your-app.com/tenant1" returnUrl="https://your-app.com/tenant1" publicOrigin="https://your-app.com">
  <identityProviders>
    <!-- 租户1的IDP -->
    <add entityId="https://idp-tenant1.com/Metadata" signOnUrl="https://idp-tenant1.com/" allowUnsolicitedAuthnResponse="true" binding="HttpPost" metadataLocation="~/App_Data/Tenant1Metadata.xml" wantAuthnRequestsSigned="true">
      <signingCertificate storeName="AddressBook" storeLocation="LocalMachine" findValue="TENANT1CERT" x509FindType="FindBySerialNumber" />
    </add>
    <!-- 租户2的IDP -->
    <add entityId="https://idp-tenant2.com/Metadata" signOnUrl="https://idp-tenant2.com/" allowUnsolicitedAuthnResponse="true" binding="HttpPost" metadataLocation="~/App_Data/Tenant2Metadata.xml" wantAuthnRequestsSigned="true">
      <signingCertificate storeName="AddressBook" storeLocation="LocalMachine" findValue="TENANT2CERT" x509FindType="FindBySerialNumber" />
    </add>
  </identityProviders>
</sustainsys.saml2>

2. 在SignIn动作中指定IDP

修改SignIn方法,接收idp参数(对应目标IDP的entityId),将其传入请求数据中,Sustainsys.Saml2会自动匹配对应的IDP:

public ActionResult SignIn(string idp)
{
    var requestData = Request.ToHttpRequestData();
    
    // 传入指定的IDP标识
    if (!string.IsNullOrEmpty(idp))
    {
        requestData.QueryString.Add("idp", idp);
    }
    
    var result = CommandFactory.GetCommand(CommandFactory.SignInCommandName).Run(requestData, Options);
    
    if (result.HandledResult)
    {
        throw new NotSupportedException("The MVC controller doesn't support setting CommandResult.HandledResult.");
    }            
    
    result.ApplyCookies(Response, Options.Notifications.EmitSameSiteNone(Request.UserAgent));
    return result.ToActionResult();
}

3. 多租户动态SP配置(可选)

如果每个租户需要独立的SP实体ID(entityId),不能使用静态的Options.FromConfiguration,需根据当前租户动态构建Options:

private IOptions GetTenantOptions(string tenantId)
{
    var options = new Sustainsys.Saml2.Configuration.Options();
    
    // 根据租户ID加载对应的SP配置
    options.SPOptions.EntityId = new EntityId($"https://your-app.com/{tenantId}");
    options.SPOptions.ReturnUrl = new Uri($"https://your-app.com/{tenantId}");
    options.SPOptions.PublicOrigin = new Uri($"https://your-app.com");
    
    // 加载该租户对应的IDP配置(可从数据库、配置文件读取)
    var idp = new IdentityProvider(new EntityId($"https://idp-{tenantId}.com/Metadata"), options.SPOptions)
    {
        SignOnUrl = new Uri($"https://idp-{tenantId}.com/"),
        AllowUnsolicitedAuthnResponse = true,
        Binding = Saml2Binding.HttpPost,
        WantAuthnRequestsSigned = true
    };
    // 添加签名证书
    idp.SigningKeys.AddConfiguredKey(new X509CertificateStoreConfig
    {
        StoreName = StoreName.AddressBook,
        StoreLocation = StoreLocation.LocalMachine,
        FindValue = $"TENANT{tenantId}CERT",
        X509FindType = X509FindType.FindBySerialNumber
    });
    
    options.IdentityProviders.Add(idp);
    return options;
}

在SignIn动作中调用此方法获取对应租户的Options:

public ActionResult SignIn(string tenantId, string idp)
{
    var options = GetTenantOptions(tenantId);
    var requestData = Request.ToHttpRequestData();
    
    if (!string.IsNullOrEmpty(idp))
    {
        requestData.QueryString.Add("idp", idp);
    }
    
    var result = CommandFactory.GetCommand(CommandFactory.SignInCommandName).Run(requestData, options);
    
    // 后续代码不变
}

二、解决RelayData在Acs动作中为null的问题

1. 正确设置RelayData的时机

确保在ApplyCookies之前设置RelayData,因为ApplyCookies会将RelayData写入临时Cookie,供ACS动作读取:

public ActionResult SignIn(string idp)
{
    var requestData = Request.ToHttpRequestData();
    
    if (!string.IsNullOrEmpty(idp))
    {
        requestData.QueryString.Add("idp", idp);
    }
    
    var result = CommandFactory.GetCommand(CommandFactory.SignInCommandName).Run(requestData, Options);
    
    if (result.HandledResult)
    {
        throw new NotSupportedException("The MVC controller doesn't support setting CommandResult.HandledResult.");
    }            
    
    // 在ApplyCookies之前设置RelayData
    result.RelayData = new Dictionary<string, string> { { "ABC", "123" } };
    
    result.ApplyCookies(Response, Options.Notifications.EmitSameSiteNone(Request.UserAgent));
    return result.ToActionResult();
}

2. 在ACS动作中读取RelayData

确保ACS动作正确处理CommandResult并读取RelayData:

public ActionResult Acs()
{
    var result = CommandFactory.GetCommand(CommandFactory.AcsCommandName).Run(Request.ToHttpRequestData(), Options);
    
    // 读取RelayData
    if (result.RelayData.TryGetValue("ABC", out string abcValue))
    {
        // 使用自定义数据abcValue
    }
    
    result.ApplyCookies(Response, Options.Notifications.EmitSameSiteNone(Request.UserAgent));
    return result.ToActionResult();
}

3. 排查Cookie问题

如果仍为null,检查以下几点:

  • 确保Cookie策略允许写入RelayData的临时Cookie(注意SameSite、Secure属性)
  • 确认请求在同一域名下(跨域会导致Cookie无法传递)
  • 可手动指定RelayDataCookie的配置:
options.RelayDataCookie.Name = "Saml2RelayData";
options.RelayDataCookie.Secure = true;
options.RelayDataCookie.SameSite = SameSiteMode.Lax;

内容的提问来源于stack exchange,提问作者centralscru

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 02:22:05