Аутентификация пользователя ASP.NET в нескольких проектах - PullRequest
3 голосов
/ 23 января 2012

Я создаю пользовательский интерфейс ASP.NET в существующей системе, которая состоит из отдельных баз данных SQL-сервера для каждого проекта. В «корпоративной» базе данных перечислены все текущие проекты, что позволяет анонимным пользователям выбирать проект для работы. Имя проекта хранится в переменной сеанса. Когда требуется вход в систему, имя пользователя / пароль / роли и т. Д. Получают из базы данных, указанной в имени проекта. Для этого я реализовал свои собственные базовые поставщики членства и ролей с изменениями в web.config, чтобы указать роли, необходимые для определенных страниц. (Я не использую стандартный инструмент конфигурации ASP.NET для управления пользователями, у меня есть приложения, которые работают с моими пользовательскими таблицами).

Сначала все это работало, но я обнаружил, что переменные сеанса еще не загружены в то время, когда система авторизации проверяет роли, к которым принадлежит текущий пользователь, чтобы определить, доступна ли страница. Так что если у нас есть в web.config, то система авторизации запускается до загрузки данных сеанса и, следовательно, до того, как я узнаю, какую базу данных проекта следует использовать.

[В частности: HttpContext.Current.Session является нулевым, когда выполняется вызов RoleProvider.GetRolesForUser]

Любой, кто занимался этой проблемой, должен точно знать, о чем я говорю. Поэтому мои вопросы:

A) Какое решение «Best Practice» для этого сценария?

B) Могу ли я хранить имя проекта в другом месте (не в переменной сеанса), которое доступно на этапе авторизации?

[Обновление: да - мы можем использовать файлы cookie, при условии, что нам не требуется операция без файлов cookie]

C) Есть ли способ получить переменную сеанса вручную в это более раннее время?

Я попробовал опцию кеширования ролей в куки, но после нескольких минут тестирования с этой опцией я обнаружил, что GetRolesForUsers все еще вызывается.

Спасибо

Обновление:
Вот еще одно описание корневой проблемы, которое предлагает «Приложение может кэшировать эту информацию в объектах Cache или Application.»:
http://connect.microsoft.com/VisualStudio/feedback/details/104452/session-is-null-in-call-to-getrolesforuser

Обновление:
Это похоже на ту же проблему, найденную здесь:
Расширение RoleProvider GetRolesForUser ()

Обновление:
Было предложено использовать UserData в FormsAuthenticationTicket, но мне требуются эти данные, даже если они не вошли в систему.

Ответы [ 2 ]

2 голосов
/ 01 марта 2012

ОБНОВЛЕНИЕ: в наши дни я решаю эту проблему гораздо проще, используя сертификат SSL с подстановочными знаками, который позволяет мне настраивать субдомены для каждого проекта, таким образом, выбор проекта указывается непосредственно в URL (и каждый проект получает свой собственный субдомен),Я по-прежнему использую cookie-файлы исключительно для тестирования при работе на локальном хосте, где у нас нет поддоменов.

Исходное решение:

Я не нашел ни одной "наилучшей практики", описывающей этот сценарий, но вот что я остановился на:

1) ЧтобыПоддержка анонимных пользователей, переключающихся между проектами (например, базы данных SQL). Я просто использую переменную сеанса для отслеживания выбора проекта.У меня есть глобальное свойство, которое использует этот выбор проекта для обслуживания соответствующей строки соединения SQL, как и когда это требуется.

2) Для поддержки вызова GetRolesForUser () на страницах, к которым применяются ограничения роли.из них мы не можем использовать переменную сеанса, потому что, как указано, переменная сеанса еще не была инициализирована, когда фактически вызывается GetRolesForUser () (и я не нашел способа заставить ее существовать на этом раннем этапе цикла запроса).

3) Единственный вариант - использовать файл cookie или поле UserData билета проверки подлинности с помощью форм.Я пролистал множество теорий об использовании сеанса / cookie / идентификаторов, связанных с объектом, хранящимся в кэше приложения (который доступен, когда сеанс недоступен), но в конечном итоге правильный выбор - поместить эти данные в билет аутентификации.

4) Если пользователь вошел в проект с помощью пары ProjectName / UserName, следовательно, где бы мы ни отслеживали аутентификацию пользователя, нам требуются обе эти данные.В обычном тестировании мы можем избежать использования имени пользователя в заявке и имени проекта в отдельном файле cookie, однако они могут быть не синхронизированы.Например, если мы используем файл cookie сеанса для имени проекта и ставим галочку «запомнить меня» при входе в систему (создавая постоянный файл cookie для билета проверки подлинности), мы можем получить имя пользователя, но не имя проекта, когда срок действия файла cookie сеанса истечет (браузер закрыт).Поэтому я вручную добавляю имя проекта в поле UserData билета аутентификации.

5) Я не понял, как манипулировать полем UserData без явной установки cookie, что означает, что мое решение не может работать в "режим без cookie.

Окончательный код оказался относительно простым.

Я переопределяю событие Authenticate в LoginView на странице входа в систему:

    //
    // 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
    // /170824/ne-udaetsya-ustanovit-formsauthenicationticket-userdata-v-rezhime-bez-failov-cookie
    //
    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));
        }
    }

Iесть этот глобальный метод для возврата имени проекта:

    /// <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;
            }
        }
    }
1 голос
/ 31 января 2012

Не можете ли вы отослать выбор проекта на какую-либо страницу, добавить этот выбор к сеансу, а затем перенаправить на соответствующую защищенную страницу, где аутентификация сработает и принудительно войдет в систему?

Сеанс ASP.NET не создается в виде файла cookie, пока вы не поместите в него хотя бы один элемент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...