【问题标题】:ASP.NET User Authentication across multiple projects跨多个项目的 ASP.NET 用户身份验证
【发布时间】:2012-01-23 09:23:58
【问题描述】:

我正在现有系统上构建 ASP.NET UI,该系统由每个项目的单独 SQL 服务器数据库组成。 “企业”数据库列出了所有当前项目,允许匿名用户选择要工作的项目。项目名称存储在会话变量中。当需要登录时,用户名/密码/角色等从项目名称指示的数据库中获取。为此,我实现了自己的基本成员资格和角色提供程序,并在 web.config 中进行了更改以指定特定页面所需的角色。 (我不使用标准的 ASP.NET 配置工具来管理用户,我有现有的应用程序可以处理我的用户表)。

这一切最初似乎都有效,但我发现在授权系统检查当前用户所属的角色以确定页面是否可访问时,会话变量尚未加载。因此,如果我们在 web.config 中有一个 ,那么授权系统会在加载会话数据之前触发,因此在我知道应该使用哪个项目数据库之前。

[特别是:调用 RoleProvider.GetRolesForUser 时 HttpContext.Current.Session 为 null]

任何解决过这个问题的人都应该清楚我在说什么。因此,我的问题是:

A) 这种情况的“最佳实践”解决方案是什么?

B) 我可以将项目名称存储在授权阶段可用的其他位置(不在会话变量中)吗?

[更新:是的 - 我们可以使用 cookie,假设我们不需要无 cookie 操作]

C) 有没有办法在这个更早的时间手动获取会话变量?

我尝试了一个在 cookie 中缓存角色的选项,但在使用该选项测试几分钟后,我发现 GetRolesForUsers 仍在被调用。

谢谢

更新:
这是对根本问题的另一种描述,它表明“应用程序可以将此信息缓存在缓存或应用程序对象中。”:
http://connect.microsoft.com/VisualStudio/feedback/details/104452/session-is-null-in-call-to-getrolesforuser

更新:
这看起来与此处发现的问题相同:
Extending the RoleProvider GetRolesForUser()

更新:
有一个关于在 FormsAuthenticationTicket 中使用 UserData 的建议,但即使没有登录,我也需要这些数据。

【问题讨论】:

  • 你在哪里设置身份验证?如有必要,我倾向于在 MemberShip Provider 中设置会话,它通常可以工作。
  • 调用 RoleProvider.GetRolesForUser() 时,我发现 HttpContext.Current.Sessions 为空,这意味着我不知道在哪个数据库中查找用户详细信息。
  • 我已经解决了使用 cookie 的主要问题。我没有将项目名称保存到 Session[],而是将其保存到 Response.Cookies[],然后在 GetRolesForUser() 中使用 HttpContext.Current.Request.Cookies["ProjectName"].value。就我目前的目的而言,这似乎可行。但是,我看到了这种破坏性的无 cookie 功能。

标签: c# asp.net authorization


【解决方案1】:

更新:这些天我通过使用通配符 SSL 证书以更简单的方式解决了这个问题,该证书允许我为每个项目配置子域,因此项目选择直接在 URL 中指定(每个项目都有自己的子域) .在没有子域的本地主机上运行时,我仍然纯粹出于测试目的使用 cookie hack。

原解决方案:

我还没有找到任何关于这种情况的“最佳实践”文章,但这是我已经确定的:

1) 为了支持匿名用户在项目(即 SQL 数据库)之间切换,我简单地使用会话变量来跟踪项目选择。我有一个全局属性,它使用此项目选择在需要时提供相应的 SQL 连接字符串。

2) 为了支持在应用了角色限制的页面上调用 GetRolesForUser(),我们不能使用会话变量,因为如上所述,当实际调用 GetRolesForUser() 时会话变量尚未初始化(而且我没有办法在请求周期的早期阶段强制它存在)。

3) 唯一的选择是使用 cookie,或使用 Forms Authentication 票证的 UserData 字段。我浏览了许多关于使用链接到存储在应用程序缓存中的对象的会话/cookie/ID 的理论(当会话不存在时可用),但最终正确的选择是将这些数据放在身份验证票中。

4) 如果用户通过 ProjectName/UserName 对登录到项目,因此我们在跟踪用户身份验证的任何地方都需要这两个数据。在简单的测试中,我们可以使用票证中的用户名和单独的 cookie 中的项目名称,但是它们可能会不同步。例如,如果我们为项目名称使用会话 cookie,并在登录时勾选“记住我”(为身份验证票创建一个永久 cookie),那么当会话 cookie 过期(浏览器关闭)时,我们可以得到一个用户名但没有项目名称)。因此,我手动将项目名称添加到身份验证票的 UserData 字段中。

5) 我还没有弄清楚如何在不明确设置 cookie 的情况下操作 UserData 字段,这意味着我的解决方案无法在“无cookie”会话模式下工作。

最终的代码结果还是比较简单的。

我在登录页面覆盖了LoginView的Authenticate事件:

    //
    // Add project name as UserData to the authentication ticket.
    // This is especially important regarding the "Remembe Me" cookie - when the authentication
    // is remembered we need to know the project and user name, otherwise we end up trying to 
    // use the default project instead of the one the user actually logged on to.
    //
    // http://msdn.microsoft.com/en-us/library/kybcs83h.aspx
    // http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.remembermeset(v=vs.100).aspx
    // http://www.hanselman.com/blog/AccessingTheASPNETFormsAuthenticationTimeoutValue.aspx
    // http://www.csharpaspnetarticles.com/2009/02/formsauthentication-ticket-roles-aspnet.html
    // http://www.hanselman.com/blog/HowToGetCookielessFormsAuthenticationToWorkWithSelfissuedFormsAuthenticationTicketsAndCustomUserData.aspx
    // http://stackoverflow.com/questions/262636/cant-set-formsauthenicationticket-userdata-in-cookieless-mode
    //
    protected void LoginUser_Authenticate(object sender, AuthenticateEventArgs e)
    {
        string userName = LoginUser.UserName;
        string password = LoginUser.Password;
        bool rememberMe = LoginUser.RememberMeSet;
        if ( [ValidateUser(userName, password)] )
        {
            // Create the Forms Authentication Ticket
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                1,
                userName,
                DateTime.Now,
                DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
                rememberMe,
                [ ProjectName ],
                FormsAuthentication.FormsCookiePath);

            // Create the encrypted cookie
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
            if (rememberMe)
                cookie.Expires = DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes);

            // Add the cookie to user browser
            Response.Cookies.Set(cookie);

            // Redirect back to original URL 
            // Note: the parameters to GetRedirectUrl are ignored/irrelevant
            Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, rememberMe));
        }
    }

我有这个全局方法来返回项目名称:

    /// <summary>
    /// SQL Server database name of the currently selected project.
    /// This name is merged into the connection string in EventConnectionString.
    /// </summary>
    public static string ProjectName
    {
        get
        {
            String _ProjectName = null;
            // See if we have it already
            if (HttpContext.Current.Items["ProjectName"] != null)
            {
                _ProjectName = (String)HttpContext.Current.Items["ProjectName"];
            }
            // Only have to do this once in each request
            if (String.IsNullOrEmpty(_ProjectName))
            {
                // Do we have it in the authentication ticket?
                if (HttpContext.Current.User != null)
                {
                    if (HttpContext.Current.User.Identity.IsAuthenticated)
                    {
                        if (HttpContext.Current.User.Identity is FormsIdentity)
                        {
                            FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
                            FormsAuthenticationTicket ticket = identity.Ticket;
                            _ProjectName = ticket.UserData;
                        }
                    }
                }
                // Do we have it in the session (user not logged in yet)
                if (String.IsNullOrEmpty(_ProjectName))
                {
                    if (HttpContext.Current.Session != null)
                    {
                        _ProjectName = (string)HttpContext.Current.Session["ProjectName"];
                    }
                }
                // Default to the test project
                if (String.IsNullOrEmpty(_ProjectName))
                {
                    _ProjectName = "Test_Project";
                }
                // Place it in current items so we do not have to figure it out again
                HttpContext.Current.Items["ProjectName"] = _ProjectName;
            }
            return _ProjectName;
        }
        set 
        {
            HttpContext.Current.Items["ProjectName"] = value;
            if (HttpContext.Current.Session != null)
            {
                HttpContext.Current.Session["ProjectName"] = value;
            }
        }
    }

【讨论】:

    【解决方案2】:

    您不能将项目选择回发到某个页面,将该选择添加到会话中,然后重定向到适当的受保护页面,授权将在其中启动并强制登录吗?

    在您将至少一项放入其中之前,ASP.NET 会话不会以 cookie 的形式创建。

    【讨论】:

    • 使用 Session[] 不起作用,因为如果我们在 web.config 文件中为某个页面添加角色限制,在 Session[ 之前触发角色提供者确定当前用户的角色] 数据已确定(在调用 GetRolesForUser() 期间 HttpContext.Current.Sessions 为空)。但是,我可以使用 cookie 存储数据并在 GetRolesForUser() 期间检索数据。
    • 我以为您说下拉菜单在匿名页面上 - 这意味着您不知道用户是谁,更不用说他们的角色了。但是,我很高兴你明白了......
    • 这是正确的 - 项目选择与用户登录无关,因此匿名用户可能已经选择了项目(我将其粘贴在 cookie 中)。当我们转到需要某个角色(现在已登录的授权用户)的页面时,我发现在 ASP.NET 之前的页面生命周期中,用户是否属于此所需角色(调用 GetRolesForUser)的问题已填充 Session[] 数据(Session 为空)。因此,我可以使用 cookie 但不能使用会话变量。
    • 您可以,或者您可以将该值存储到中间匿名页面上的会话(或在回发期间,在下拉列表所在的同一页面上,然后将重定向)。无论如何,同样的事情——关键是让它发挥作用;方式差别不大。
    猜你喜欢
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-27
    • 2020-11-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    相关资源
    最近更新 更多