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 и все!
Ваша стратегия такова:
Вы не знаете, кто пользователь, потому что его нет в вашей таблице пользователей, в группе ActiveDirectory или где-либо еще. В этом случае вы даете им роль "гостя" и соответственно обрезаете пользовательский интерфейс. Может быть, дать им ссылку на электронную почту, чтобы запросить доступ.
У вас есть пользователь в списке пользователей, но ему не назначена роль. Так что дайте им роль "пользователя" и обрежьте пользовательский интерфейс, как указано выше. Может быть, они могут видеть свои вещи, но больше ничего.
Пользователь находится в вашем списке и ему назначена роль. Первоначально вы будете назначать эту роль, вручную обновляя таблицу 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 для проверки подлинности своих пользователей - это просто означает, что вам придется немного поднять себя.