ServiceStack.NET Windows Аутентификация (NTLM) в ASP.NET MVC

Как реализовать аутентификацию Windows в сборке проекта ServiceStack на ASP.NET MVC4?

Я начал с глобального фильтра запросов, добавленного в AppHost:

private void ConfigureAuth(Funq.Container container)
{
    this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
    {
        var user = HttpContext.Current.User.Identity;
        if (!user.IsAuthenticated ||
            !user.Name.Contains(_myTestUser)) //todo: check username here in database (custom logic) if it has access to the application
            httpResp.ReturnAuthRequired();
    });
}

Откроется диалоговое окно входа в систему, которое при правильном вводе (имя пользователя существует и вводится действительный пароль, а также myTestUser установлен на это), приводит к успешному ответу. Если что-то не так, снова открывается диалоговое окно входа. - Это звучит нормально для меня. Но после повторного ввода правильного пользователя в этом втором окне входа в систему оно перестает работать. Диалоговое окно открывается снова, если оно снова неверно. Внутри функции фильтра не задана точка останова.

Есть идеи, что может вызвать это?

Вот что я добавил в web.config:

<authentication mode="Windows"/>
<authorization>
  <deny users="?" /> <!--only allow authenticated users-->
</authorization>

Я хочу полностью заблокировать сайт и разрешить доступ к указанным пользователям Windows в базе данных только с их конкретными разрешениями (ролями). Мне нужно реализовать собственную логику для доступа к "списку пользователей и ролей". Может быть, есть другой способ сделать это в MVC4/ ASP.NET?

4 ответа

Решение

Настраиваемая аутентификация ServiceStack для интрасетей Windows

Я бился головой об этом весь день и придумал следующее.

Первый вариант использования:

Вы находитесь в корпоративной интрасети, используя проверку подлинности Windows. Вы установили режим аутентификации ="Windows" в своем файле web.config и все!

Ваша стратегия такова:

  1. Вы не знаете, кто пользователь, потому что его нет в вашей таблице пользователей, в группе ActiveDirectory или где-либо еще. В этом случае вы даете им роль "гостя" и соответственно обрезаете пользовательский интерфейс. Может быть, дать им ссылку на электронную почту, чтобы запросить доступ.

  2. У вас есть пользователь в списке пользователей, но ему не назначена роль. Так что дайте им роль "пользователя" и обрежьте пользовательский интерфейс, как указано выше. Может быть, они могут видеть свои вещи, но больше ничего.

  3. Пользователь находится в вашем списке и ему назначена роль. Первоначально вы будете назначать эту роль, вручную обновляя таблицу UserAuth в базе данных. В конце концов у вас будет сервис, который сделает это для авторизованных пользователей.

Итак, давайте перейдем к коду.

Сторона сервера

В слое ServiceStack Service мы создаем поставщика авторизации пользовательских учетных данных в соответствии с https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization

      public class CustomCredentialsAuthProvider : CredentialsAuthProvider
        {
            public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
            {
                //NOTE: We always authenticate because we are always a Windows user! 
                // Yeah, it's an intranet  
                return true;
            }

            public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
            {

                // Here is why we set windows authentication in web.config
                var userName = HttpContext.Current.User.Identity.Name;

                //  Strip off the domain
                userName = userName.Split('\\')[1].ToLower();

                // Now we call our custom method to figure out what to do with this user
                var userAuth = SetUserAuth(userName);

                // Patch up our session with what we decided
                session.UserName = userName;
                session.Roles = userAuth.Roles;            

                // And save the session so that it will be cached by ServiceStack 
                authService.SaveSession(session, SessionExpiry);
            }

        }

А вот наш пользовательский метод:

     private UserAuth SetUserAuth(string userName)
            {
                // NOTE: We need a link to the database table containing our user details
                string connStr = ConfigurationManager.ConnectionStrings["YOURCONNSTRNAME"].ConnectionString;
                var connectionFactory = new OrmLiteConnectionFactory(connStr, SqlServerDialect.Provider);

                // Create an Auth Repository
                var userRep = new OrmLiteAuthRepository(connectionFactory);

                // Password not required. 
                const string password = "NotRequired";

                // Do we already have the user? IE In our Auth Repository
                UserAuth userAuth = userRep.GetUserAuthByUserName(userName);

                if (userAuth == null ){ //then we don't have them}

                // If we don't then give them the role of guest
                userAuth.Roles.Clear();
                userAuth.Roles.Add("guest")

                // NOTE: we are only allowing a single role here               

                // If we do then give them the role of user
                // If they are one of our team then our administrator have already given them a role via the setRoles removeRoles api in ServiceStack
               ...

                // Now we re-authenticate out user
                // NB We need userAuthEx to avoid clobbering our userAuth with the out param
                // Don't you just hate out params?

                // And we re-authenticate our reconstructed user
                UserAuth userAuthEx;
                var isAuth = userRep.TryAuthenticate(userName, password, out userAuthEx);
                return userAuth;
            }

В appHost Configure добавьте следующие ResponseFilters в конце функции

    ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-Role",request.GetSession(false).Roles[0]));
    ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-AccountName", request.GetSession(false).UserName));

Это отправляет некоторые дополнительные заголовки клиенту, чтобы мы могли обрезать пользовательский интерфейс в соответствии с ролью пользователя.

Сторона клиента

На стороне клиента, когда мы делаем первый запрос к серверу, мы размещаем имя пользователя и пароль в соответствии с требованиями пользовательской аутентификации. Для обоих параметров установлено значение "NotRequired", поскольку мы будем знать, кто является пользователем на стороне сервера, через HttpContext.Current.User.Identity.Name.

Следующее использует AngularJS для связи AJAX.

    app.run(function($templateCache, $http, $rootScope) {

        // Authenticate and get X-Role and X-AccountName from the response headers and put it in $rootScope.role

        // RemeberMe=true means that the session will be cached 
        var data={"UserName" : "NotRequired", "Password" : "NotRequired", "RememberMe": true };

        $http({ method : 'POST', url : '/json/reply/Auth', data : data }).
            success(function (data, status, headers, config) {
            // We stash this in $rootScope for later use!
                $rootScope.role = headers('X-Role');
                $rootScope.accountName = headers('X-AccountName');
                console.log($rootScope.role);
                console.log($rootScope.role);
            }).
            error(function (data, status, headers, config) {
                // NB we should never get here because we always authenticate
                toastr.error('Not Authenticated\n' + status, 'Error');
            });
    };

Вероятно, стоит отметить, что начиная с версии 4.0.21. Поставщик проверки подлинности Windows был реализован, как показано здесь: https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#windows-auth-provider-for-aspnet

ОБНОВЛЕНИЕ Проверьте примечание @BiffBaffBoff ниже. Похоже, Windows Auth получил в прокат.

Я реализовал довольно простой провайдер NTLM Auth. Если у меня будет время, я заверну это в плагине и опубликую на GitHub. Но сейчас:

web.config - это предотвращает подключение анонимных пользователей (как указано в вопросе):

<system.web>
  <authentication mode="Windows" />
  <authorization>
    <deny users="?" />
  </authorization>
...
</system.web>

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

public class NTLMAuthProvider : CredentialsAuthProvider
{
    public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
    {
        return !string.IsNullOrWhiteSpace(session.UserName);
    }
}

И фильтр предварительного запроса для проверки личности, предоставленной IIS:

        this.PreRequestFilters.Add((req, resp) =>
        {
            IAuthSession session = req.GetSession();
            if (session.UserName == null)
            {
                session.UserName = ((HttpRequestWrapper)req.OriginalRequest).LogonUserIdentity.Name;

                // Add permissions & roles here - IUserAuthRepository, ICacheClient, etc.

                req.SaveSession(session);
            }
        });

Вы включили олицетворение в элементе system.web вашего файла конфигурации?

<identity impersonate="true"/>

Это может привести ко второму сбою, если что-то пытается получить доступ к ограниченному ресурсу.

Вы упомянули о желании реализовать пользовательскую логику, чтобы определить, какие пользователи могут получить доступ к системе. Я предполагаю что

<allow roles="DomainName\WindowsGroup" />
<deny users="*" />

недостаточно Если это хорошо, но в противном случае, вы также можете реализовать собственный поставщик ролей, который может вам помочь. Это требует использования проверки подлинности с помощью форм вместо Windows, но это не обязательно означает, что вы не можете использовать учетные данные Windows для проверки подлинности своих пользователей - это просто означает, что вам придется немного поднять себя.

Другие вопросы по тегам