多租户ASP.NET MVC应用程序的跨域OWIN身份验证

时间:2016-04-21 17:26:27

标签: c# asp.net asp.net-mvc owin multi-tenant

我正在为多租户ASP.NET MVC应用程序使用OWIN身份验证。

应用程序和身份验证位于单个应用程序中的一台服务器上,但可以通过许多域和子域进行访问。例如:

www.domain.com
site1.domain.com
site2.domain.com
site3.domain.com
www.differentdomain.com
site4.differentdomain.com
site5.differentdomain.com
site6.differentdomain.com

我想允许用户登录这些域中的任何域,并且无论使用哪个域来访问应用程序,都可以使用其身份验证cookie。

这就是我的身份验证设置:

public void ConfigureAuthentication(IAppBuilder Application)
{
    Application.CreatePerOwinContext<RepositoryManager>((x, y) => new RepositoryManager(new SiteDatabase(), x, y));

    Application.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        CookieName = "sso.domain.com",
        CookieDomain = ".domain.com",
        LoginPath = new PathString("/login"),
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,  
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentityCallback: (manager, user) => user.GenerateClaimsAsync(manager),
                getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
        }
    });

    Application.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}

我还在我的应用程序的根web.config中为我的应用程序显式设置了一个机器密钥:

<configuration>
    <system.web>
        <machineKey decryption="AES" decryptionKey="<Redacted>" validation="<Redacted>" validationKey="<Redacted>" />
    </system.web>
</configuration>

更新

当我在domain.com和site1.domain.com之间导航时,此设置按预期工作,但现在它不允许我登录differentdomain.com。

我了解Cookie与单个域绑定。但是,在多个域中持久登录的最简单方法是什么?有没有办法让我从不同的域读取cookie,解密它,并为differentdomain.com重新创建一个新的cookie?

4 个答案:

答案 0 :(得分:8)

由于您需要简单的事情,请考虑这一点。在您的特定设置中,您实际上只有一个应用程序可以通过多个域名访问,您可以进行简单的单点登录&#34;。首先,您必须选择负责初始身份验证的单个域名。我们说这是auth.domain.com(请记住它只是域名 - 您的所有域名仍然指向单个应用程序)。然后:

  1. 假设用户在domain1.com,您发现他没有登录(没有cookie)。您将他引导至auth.domain.com登录页面。
  2. 假设您已经登录。您看到该请求来自domain1.com(通过Referrer标头,或者您可以显式传递域)。您验证这是您的可信域(重要),并生成如下的身份验证令牌:

    var token = FormsAuthentication.Encrypt(
        new FormsAuthenticationTicket(1, "username", DateTime.Now, DateTime.Now.AddHours(8), true, "some relevant data"));
    

    如果您不使用表单身份验证 - 只需使用计算机密钥保护某些数据:

    var myTicket = new MyTicket()
    {
        Username = "username",
        Issued = DateTime.Now,
        Expires = DateTime.Now.AddHours(8),
        TicketExpires = DateTime.Now.AddMinutes(1)
    };
    using (var ms = new MemoryStream()) {
        new BinaryFormatter().Serialize(ms, myTicket);
        var token = Convert.ToBase64String(MachineKey.Protect(ms.ToArray(), "auth"));
    }
    

    所以基本上你以与asp.net相同的方式生成你的令牌。由于您的网站都在同一个应用程序中 - 无需担心不同的机器密钥。

  3. 您将用户重定向回domain1.com,在查询字符串中传递加密的令牌。有关此问题的安全隐患,请参阅here。当然我想你使用https,否则没有设置(无论是#34;单点登录&#34;还是没有)都是安全的。这在某些方面类似于asp.net&#34; cookieless&#34;认证

  4. domain1.com上,您会看到该令牌并验证:

    var ticket = FormsAuthentication.Decrypt(token);
    var userName = ticket.Name;
    var expires = ticket.Expiration;
    

    或者用:

    var unprotected = MachineKey.Unprotect(Convert.FromBase64String(token), "auth");
    using (var ms = new MemoryStream(unprotected)) {
        var ticket = (MyTicket) new BinaryFormatter().Deserialize(ms);
        var user = ticket.Username;
    }
    
  5. 您使用在令牌中收到的信息在domain1.com上创建Cookie,并将用户重定向回最初来自的位置。
  6. 所以有一堆重定向,但至少用户只需输入一次密码。

    更新以回答您的问题。

    1. 如果您发现该用户在domain1.com上经过身份验证,则会重定向到auth.domain.com。但是在auth.domain.com重定向后使用令牌 - 您像往常一样在domain1.com上创建一个cookie,用户登录到domain1.com。因此,每个用户只需执行一次重定向(就像通常的登录一样)。

    2. 您可以使用javascript(XmlHttpRequest或jquery.get \ post方法)向auth.domain.com发出请求。但请注意,您必须配置CORS才能允许(例如,请参阅here)。什么是CORS简称?当通过来自siteA(另一个域)的javascript请求siteB时 - 浏览器将首先询问siteB是否信任siteA来发出此类请求。它通过添加特殊标头来进行请求,并希望在响应中看到一些特殊标头。您需要添加的标头允许domain1.com通过javascript请求auth.domain.com。完成后 - 从domain1.com javascript到auth.domain.com发出此类请求,如果已登录 - auth.domain.com将返回您的令牌,如上所述。然后使用该令牌对domain1.com进行查询(再次使用javascript),以便domain1.com可以设置响应中的cookie。现在您使用cookie登录domain1.com并可以继续。 为什么我们需要这一切,即使我们有一个应用程序可以从不同的域访问?因为浏览器不知道并且完全不同于它们。除此之外 - http协议是无状态的,并且每个请求都与其他任何请求无关,因此我们的服务器还需要确认请求A和B由同一个用户创建,因此这些令牌。

    3. 是的,HttpServerUtility.UrlTokenEncode在这里使用完全没问题,甚至比Convert.ToBase64String还要好,因为无论如何都需要对其进行url编码(在查询字符串中传递它)。但是,如果您不在查询字符串中传递令牌(例如,您将使用上面的javascript方式 - 您不需要对其进行网址编码,因此在这种情况下不要使用HttpServerUtility.UrlTokenEncode

答案 1 :(得分:1)

你对cookie的运作方式是对的,但不是OWIN的工作方式。

不要覆盖Auth服务器的cookie域(auth.domain.com)。

您可以将各个网站的Cookie域覆盖为“site1.domain.com”和“site2.domain.com”。

在您的SSO页面中,假设有人登陆site1.domain.com,因为未经身份验证的人会被带到您的身份验证服务器。 auth服务器获取登录凭据并在注册的URI上将代码发送到site1.domain.com(例如:/ oauthcallback)。 site1.domain.com上的此端点将从代码和SignIn(自动编写cookie)获取访问令牌。因此,2个cookie在auth.domain.com上编写,第二个在site1.domain.com上编写

现在,同一用户访问site2.domain.com并在“auth.domain.com”上找到登录用户的cookie。这意味着用户已登录并在“site2.domain.com”上创建了具有相同声明的新cookie

用户现已登录这两个站点。

您不手动编写cookie。使用OwinContext.Signin并保存/创建cookie。

答案 2 :(得分:1)

要回答有关更新的问题,无法在不同的域之间共享Cookie。

您可以使用一些查询字符串参数和一些服务器端逻辑来处理这种特殊情况,但这可能会引起一些安全问题。

请参阅此建议:https://stackoverflow.com/a/315141/4567456

<强>更新

根据您的评论,以下是详细信息:

http://blog.stackoverflow.com/2010/09/global-network-auto-login/

https://meta.stackexchange.com/questions/64260/how-does-sos-new-auto-login-feature-work

http://openid.net/specs/openid-connect-session-1_0.html

奖金:

今天使用的机制与前面两个链接中描述的机制略有不同,也更简单。

如果您在登录StackOverflow时查看网络请求,您将看到它将您单独登录到网络上的每个站点。

http://stackexchange.com/users/login/universal.gif?authToken= ....

https://serverfault.com/users/login/universal.gif?authToken= ...

https://askubuntu.com/users/login/universal.gif?authToken= ...

等等...

答案 3 :(得分:0)

威廉,

我了解Cookie与单个域绑定。

是的,你无法在客户端操纵它。浏览器永远不会将一个域的cookie发送给另一个域。

但是,在多个域中持久登录的最简单方法是什么?

外部身份提供商或安全令牌服务(STS)是实现此目的的最简单方法。在此设置中所有域名site1.com。 site2.com等将信任STS作为身份提供者。在此联合解决方案中,用户使用STS进行身份验证,并在所有域中使用联合身份。来自专家的这个主题是一个很棒的resource

我有没有办法从不同的域读取cookie,解密它,并为differentdomain.com重新创建一个新的cookie?

通过一些调整,您可以使用当前设置实现此联合解决方案。仅供参考,这不是推荐或使用中的方法,而是一个帮助您实现目标的想法。

假设您有多个域1,2,3指向单个应用程序。我将创建指向同一应用程序的另一个域STS,但仅处理cookie创建和验证。创建一个自定义中间件,利用包装下的asp.net cookie身份验证中间件。仅当请求针对STS域时才会执行此操作。这可以通过域/主机上的简单if条件或使用IAppBuilder接口上的Map来实现。

让我们看一下流程:

一个。用户尝试使用域1

访问受保护资源

湾由于他未经过身份验证,因此将重定向到域STS,其中包含domain1的查询参数(用于STS以识别他正在从哪个域访问资源)以及domain1上受保护资源的URL

℃。由于请求是针对STS域的,因此自定义中间件会启动并验证用户身份。并为STS发送两个cookie,第二个为他正在尝试的域(在这种情况下为1)发送。

d。现在,用户将被重定向到domain1上的受保护资源

即如果他试图访问域2上的受保护资源,则他未被验证,因此将被重定向到STS。

F。因为他有一个STS的身份验证cookie,它将通过浏览器附加到STS的请求。 STS中间件可以验证cookie并可以验证用户。如果进行身份验证,则为域2发出另一个cookie,并将其重定向到domain2上的受保护资源。

如果仔细查看流程,它与我们拥有外部STS时的操作类似,但在我们的案例中,STS是我们的应用程序。我希望这是有道理的。

如果我必须执行此任务,我将使用位于同一主机(IIS)上的外部STS。 IdentityServer是OpenID Connect标准的开源实现,我将其用作STS。它在使用方面非常灵活,可以与我们的应用程序共同托管(我认为这在您的案例中非常有用)。以下是Identity serverVideo

链接

我希望这有用。请让我知道,如果你有任何问题。

谢谢你, 索马。

相关问题