Мне интересно, может ли кто-нибудь указать мне направление или пример, в котором есть законченный код, чтобы я мог получить общее представление?
Спасибо.
Обновлять: У меня есть только следующий фрагмент кода в Startup.cs и я проверяю, что windowsAutication истинно в launchSettings.json.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
//.RequireRole(@"Departmental - Information Technology - Development") // Works
.RequireRole(@"*IT.Center of Excellence.Digital Workplace") // Error
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
}
Думаю, я включил аутентификацию и пытается разрешить пользователям, входящим в указанную группу AD, иметь доступ к приложению на глобальном уровне.
Если я использую закомментированный RequireRole, он работает, но использую раскомментированный RequireRole, он дает мне эту ошибку: Win32Exception: не удалось установить доверительные отношения между основным доменом и доверенным доменом.
Верхняя строка в стопке показывает: System.Security.Principal.NTAccount.TranslateToSids (IdentityReferenceCollection sourceAccounts, из bool someFailed)
Есть идеи, почему?
Насколько я понимаю из обновления выше
Похоже, что имя группы, указанное в RequireRole, является списком рассылки электронной почты, а не группой безопасности. Если я использую другую группу AD, она работает, но с этой новой ошибкой:
InvalidOperationException: не указана схема проверки подлинности и схема DefaultForbidScheme не найдена.
Если я добавлю IIS default authenticationScheme в ConfigureServices в Startup.cs
services.AddAuthentication(IISDefaults.AuthenticationScheme);
он дает мне страницу HTTP 403: веб-сайт отказался отображать эту веб-страницу
Итак, это последний код:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole(@"Departmental - Information Technology - Development") // AD security group
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
}
Поправьте меня, если я неправильно понял. Спасибо.
Спасибо, Дэвид. Я заглянул на веб-сайт Microsoft: docs.microsoft.com/en-us/aspnet/core/security/authorization/…, не уверен, как реализация требует условия авторизации на основе утверждений. Для группы AD будет ли авторизация на основе ролей более подходящей проверкой принадлежности пользователя к группе?
авторизацию какого типа вы ищете?
Это внутреннее приложение, поэтому оно будет работать в интрасети. Я думаю, авторизация группы AD будет подходящим вариантом.
Думаю, этот пост тоже отвечает на мой вопрос: https://stackoverflow.com/questions/36413476/mvc-core-how-to -force-set-global-autorization-for-all- действия? rq = 1
Вы можете включить проверку подлинности Windows для приложений интрасети. Прочтите документы здесь. Вы можете проверить, входит ли пользователь в роль / группу, выполнив что-то вроде это.
Прежде чем вы это сделаете, вы можете проверить информацию о группах, к которым присоединился ваш компьютер, выполнив gpresult /R
в командной строке. См. эта почта для получения дополнительной информации.
User.IsInRole("xxxx") // this should return True for any group listed up there
Вам не нужно преобразовывать текущий участник в субъект Windows, если вам не нужно получать какую-либо информацию, связанную с Windows.
Если вы хотите получить список всех групп, вам все равно нужно запросить свой AD.
предупреждение:
Иногда я вижу, что некоторые группы не отображаются в результатах при использовании gpresult /R
на компьютере, по сравнению с методом варианта 2. Вот почему иногда, когда вы выполняете User.IsInRole()
, он возвращает false. Я до сих пор не знаю, почему это происходит.
Аутентификация Windows предлагает лишь небольшую информацию о пользователе и группах AD. Иногда этого достаточно, но в большинстве случаев это не так.
Вы также можете использовать обычную проверку подлинности с помощью формы и поговорить с AD внизу и выпустить файл cookie. Таким образом, хотя пользователю необходимо войти в ваше приложение, используя свои учетные данные и пароль Windows, у вас будет полный контроль над информацией AD.
Вы же не хотите писать все от руки. К счастью, здесь есть библиотека Novell.Directory.Ldap.NETStandard. Вы можете найти его в NuGet.
Интерфейсы для определения того, что вам нужно от AD, а также протокол входа в систему:
namespace DL.SO.Services.Core
{
public interface IAppUser
{
string Username { get; }
string DisplayName { get; }
string Email { get; }
string[] Roles { get; }
}
public interface IAuthenticationService
{
IAppUser Login(string username, string password);
}
}
Реализация AppUser:
using DL.SO.Services.Core;
namespace DL.SO.Services.Security.Ldap.Entities
{
public class AppUser : IAppUser
{
public string Username { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string[] Roles { get; set; }
}
}
Объект конфигурации Ldap для сопоставления значений из appsettings.json:
namespace DL.SO.Services.Security.Ldap
{
public class LdapConfig
{
public string Url { get; set; }
public string BindDn { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string SearchBase { get; set; }
public string SearchFilter { get; set; }
}
}
Реализация LdapAuthenticationService:
using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using DL.SO.Services.Core;
using DL.SO.Services.Security.Ldap.Entities;
namespace DL.SO.Services.Security.Ldap
{
public class LdapAuthenticationService : IAuthenticationService
{
private const string MemberOfAttribute = "memberOf";
private const string DisplayNameAttribute = "displayName";
private const string SAMAccountNameAttribute = "sAMAccountName";
private const string MailAttribute = "mail";
private readonly LdapConfig _config;
private readonly LdapConnection _connection;
public LdapAuthenticationService(IOptions<LdapConfig> configAccessor)
{
_config = configAccessor.Value;
_connection = new LdapConnection();
}
public IAppUser Login(string username, string password)
{
_connection.Connect(_config.Url, LdapConnection.DEFAULT_PORT);
_connection.Bind(_config.Username, _config.Password);
var searchFilter = String.Format(_config.SearchFilter, username);
var result = _connection.Search(
_config.SearchBase,
LdapConnection.SCOPE_SUB,
searchFilter,
new[] {
MemberOfAttribute,
DisplayNameAttribute,
SAMAccountNameAttribute,
MailAttribute
},
false
);
try
{
var user = result.next();
if (user != null)
{
_connection.Bind(user.DN, password);
if (_connection.Bound)
{
var accountNameAttr = user.getAttribute(SAMAccountNameAttribute);
if (accountNameAttr == null)
{
throw new Exception("Your account is missing the account name.");
}
var displayNameAttr = user.getAttribute(DisplayNameAttribute);
if (displayNameAttr == null)
{
throw new Exception("Your account is missing the display name.");
}
var emailAttr = user.getAttribute(MailAttribute);
if (emailAttr == null)
{
throw new Exception("Your account is missing an email.");
}
var memberAttr = user.getAttribute(MemberOfAttribute);
if (memberAttr == null)
{
throw new Exception("Your account is missing roles.");
}
return new AppUser
{
DisplayName = displayNameAttr.StringValue,
Username = accountNameAttr.StringValue,
Email = emailAttr.StringValue,
Roles = memberAttr.StringValueArray
.Select(x => GetGroup(x))
.Where(x => x != null)
.Distinct()
.ToArray()
};
}
}
}
finally
{
_connection.Disconnect();
}
return null;
}
private string GetGroup(string value)
{
Match match = Regex.Match(value, "^CN=([^,]*)");
if (!match.Success)
{
return null;
}
return match.Groups[1].Value;
}
}
}
Конфигурация в appsettings.json (просто пример):
{
"ldap": {
"url": "[YOUR_COMPANY].loc",
"bindDn": "CN=Users,DC=[YOUR_COMPANY],DC=loc",
"username": "[YOUR_COMPANY_ADMIN]",
"password": "xxx",
"searchBase": "DC=[YOUR_COMPANY],DC=loc",
"searchFilter": "(&(objectClass=user)(objectClass=person)(sAMAccountName = {0}))"
},
"cookies": {
"cookieName": "cookie-name-you-want-for-your-app",
"loginPath": "/account/login",
"logoutPath": "/account/logout",
"accessDeniedPath": "/account/accessDenied",
"returnUrlParameter": "returnUrl"
}
}
Настройте аутентификацию (возможно, авторизацию) для приложения:
namespace DL.SO.Web.UI
{
public class Startup
{
private readonly IHostingEnvironment _currentEnvironment;
public IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
_currentEnvironment = env;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Authentication service
services.Configure<LdapConfig>(this.Configuration.GetSection("ldap"));
services.AddScoped<IAuthenticationService, LdapAuthenticationService>();
// MVC
services.AddMvc(config =>
{
// Requiring authenticated users on the site globally
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
// You can chain more requirements here
// .RequireRole(...) OR
// .RequireClaim(...) OR
// .Requirements.Add(...)
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Authentication
var cookiesConfig = this.Configuration.GetSection("cookies")
.Get<CookiesConfig>();
services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = cookiesConfig.CookieName;
options.LoginPath = cookiesConfig.LoginPath;
options.LogoutPath = cookiesConfig.LogoutPath;
options.AccessDeniedPath = cookiesConfig.AccessDeniedPath;
options.ReturnUrlParameter = cookiesConfig.ReturnUrlParameter;
});
// Setup more authorization policies as an example.
// You can use them to protected more strict areas. Otherwise
// you don't need them.
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly",
policy => policy.RequireClaim(ClaimTypes.Role, "[ADMIN_ROLE_OF_YOUR_COMPANY]"));
// More on Microsoft documentation
// https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseMvc(...);
}
}
}
Как аутентифицировать пользователей с помощью службы аутентификации:
namespace DL.SO.Web.UI.Controllers
{
public class AccountController : Controller
{
private readonly IAuthenticationService _authService;
public AccountController(IAuthenticationService authService)
{
_authService = authService;
}
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
try
{
var user = _authService.Login(model.Username, model.Password);
// If the user is authenticated, store its claims to cookie
if (user != null)
{
var userClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(CustomClaimTypes.DisplayName, user.DisplayName),
new Claim(ClaimTypes.Email, user.Email)
};
// Roles
foreach (var role in user.Roles)
{
userClaims.Add(new Claim(ClaimTypes.Role, role));
}
var principal = new ClaimsPrincipal(
new ClaimsIdentity(userClaims, _authService.GetType().Name)
);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = model.RememberMe
}
);
return Redirect(Url.IsLocalUrl(model.ReturnUrl)
? model.ReturnUrl
: "/");
}
ModelState.AddModelError("", @"Your username or password
is incorrect. Please try again.");
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
}
return View(model);
}
}
}
Как читать информацию, хранящуюся в претензиях:
public class TopNavbarViewComponent : ViewComponent
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TopNavbarViewComponent(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<IViewComponentResult> InvokeAsync()
{
string loggedInUsername = _httpContextAccessor.HttpContext.User.Identity.Name;
string loggedInUserDisplayName = _httpContextAccessor.HttpContext.User.GetDisplayName();
...
return View(vm);
}
}
Метод расширения для ClaimsPrincipal:
namespace DL.SO.Framework.Mvc.Extensions
{
public static class ClaimsPrincipalExtensions
{
public static Claim GetClaim(this ClaimsPrincipal user, string claimType)
{
return user.Claims
.SingleOrDefault(c => c.Type == claimType);
}
public static string GetDisplayName(this ClaimsPrincipal user)
{
var claim = GetClaim(user, CustomClaimTypes.DisplayName);
return claim?.Value;
}
public static string GetEmail(this ClaimsPrincipal user)
{
var claim = GetClaim(user, ClaimTypes.Email);
return claim?.Value;
}
}
}
Как использовать авторизацию политики:
namespace DL.SO.Web.UI.Areas.Admin.Controllers
{
[Area("admin")]
[Authorize(Policy = "AdminOnly")]
public abstract class AdminControllerBase : Controller {}
}
Вы можете загрузить AD Explorer от Microsoft, чтобы визуализировать AD вашей компании.
Оппс. Я планировал просто выдать что-нибудь для старта, но в итоге написал очень длинный пост.
Спасибо, Дэвид. Итак, насколько я понимаю, ваша реализация требует выполнять поиск / поиск в AD каждый раз, когда пользователь пытается получить доступ к приложению, и настроенный код будет проверять это на глобальном уровне, верно?
@XiaoHan: только когда пользователь не прошел аутентификацию? Если пользователь уже аутентифицирован, утверждения уже хранятся в файле cookie. Теперь я понимаю, что они не будут обновляться, если администратор AD изменит информацию о пользователе. Следовательно, было бы неплохо установить срок действия cookie на разумно короткий период, чтобы приложение потребовало от пользователя достаточно быстрой повторной аутентификации и обновления заявлений.
Спасибо за ваш ответ. Я еще не зашел так далеко. Все еще изучаем варианты этого распространенного сценария работы с группой AD для внутреннего приложения. Думаю, об аутентификации позаботились, так как она внутренняя, а ядро .net имеет встроенную функцию. верно? Исправьте меня, если я ошибаюсь, чтобы заархивировать мою цель, мне нужно написать код настройки, чтобы узнать, находится ли пользователь (например, [имя домена] \ smith) в группе AD (например, [имя домена] \ dev group), верно? Пока у меня есть только части AuthorizationPolicyBuilder и AddAuthorization в ConfigureServices.
Смотрите мои обновления в посте. Да, если вы включите проверку подлинности Windows. Нет, если вы хотите использовать обычную проверку подлинности с помощью форм. В дальнейшем вам нужно будет написать часть аутентификации самостоятельно.
Спасибо. В коде поиска, который я пишу, у меня есть некоторая простая логика, но она всегда возвращает false при проверке, играет ли принципал в роли конкретной группы. Любая идея? var myIdentity = WindowsIdentity.GetCurrent (); var myPrincipal = новый WindowsPrincipal (myIdentity); вернуть myPrincipal.IsInRole (groupName);
На этот вопрос сложно ответить, потому что у меня нет доступа к активному каталогу вашей компании или я не знаю, как настроены компьютеры ваших парней. Если вы запустите командную строку и наберете gpresult /R
, вы должны увидеть список групп, к которым присоединился ваш компьютер. IsInRole()
в любой из этих групп должен возвращать True. Кстати, вам не нужно конвертировать текущего участника в Windows, если вам не нужна информация Windows. Смотрите мой обновленный пост.
Спасибо. Метод IsInRole работает при проверке по именам групп (без указания домена) с помощью команды «gpresult / R». Я сравнил имена групп, они выглядят иначе, чем то, что я вижу в проводнике AD под значением поля memberOf конкретной учетной записи. Как вы сказали, некоторые группы находятся в AD, но не отображают команду gpresult / r. Может разрешение?
Быстрый вопрос, эта строка кода в Startup.cs: var cookiesConfig = this.Configuration.GetSection ("cookies"). Get <CookiesConfig> (); Я предполагаю, что «cookies» - это имя раздела в файле конфигурации, является ли CookiesConfig настраиваемым классом для хранения данных конфигурации?
@XiaoHan: да, смотри мои обновления. И да, CookiesConfig
- это просто класс, свойства которого совпадают со свойствами, определенными в файле appsettings.json
, как и LdapConfig
.
Если я буду следовать вашему подходу, означает ли это, что мне нужно указать атрибут авторизации, как в вашем примере: [Authorize (Policy = "AdminOnly")] для всего класса контроллера? Причина, по которой я думаю об использовании глобальной аутентификации / авторизации, заключается в том, чтобы не указывать везде авторизацию на основе политики / ролей. Возможно ли, что я все делаю в одном месте?
@XiaoHan: Нет, если ваш сайт не нуждается в защите с помощью ролей / политик, кроме аутентификации. И я уже настроил глобальный фильтр, для которого требуется RequireAuthenticatedUser()
, поэтому ваш сайт должен быть защищен глобально от неаутентифицированных пользователей. Настройка политики AdminOnly
приведена в примере, чтобы продемонстрировать, как вы можете защитить определенные области вашего сайта с помощью более конкретных ролей, таких как область Admin
в примере. Обратите внимание, что даже пользователь аутентифицирован, если у него нет [ADMIN_ROLE_OF_YOUR_COMPANY]
, он не может получить доступ к области администратора. Английский - это сложно ..
Спасибо. Возможна ли авторизация на глобальном уровне? Точно так же, как аутентификация выполняется в Startup.cs, у меня есть настроенный код для проверки, принадлежит ли этот зарегистрированный пользователь к группе AD, которой сайт разрешает доступ к содержимому, если я вызываю функцию аналогичным образом в Startup.cs, Будет ли это проверять, авторизован ли вошедший в систему пользователь?
@XiaoHan: решать вам как разработчику, но если вы спросите меня, я разделю аутентификацию и авторизацию. Предыдущие структуры идентификации, такие как SimpleMembership
и даже более ранние Asp.Net Identity 1.x
, смешивали аутентификацию и авторизацию. Вы видите, что в Core 2.x они разделяются по уважительным причинам. Я имею в виду, что вы могли бы выполнить дополнительную проверку ролей внутри LdapAuthenticationService
и вернуть Null
в случае сбоя, но это не подходящее место для этого. LdapAuthenticationService
предназначен для аутентификации! Лучшее место для этого - после .RequireAuthenticatedUser()
. Смотрите обновления.
Спасибо, Дэвид. Я проверил ваши обновления в Startup.cs, так как в ваших комментариях сказано, что AddAuthorization предназначена «для защищенных более строгих областей. В противном случае они вам не нужны», поэтому, если я хочу аутентифицировать всех пользователей AD здесь, что было сделано вызывая RequireAuthenticatedUser, я хочу авторизовать только определенную группу пользователей AD, где вы рекомендуете реализовать логику? Я не уверен для каждого класса контроллера, я делаю что-то вроде проверки user.IsInGroup (groupname)? Или я могу сделать проверку в Startup.cs?
Я добавил несколько комментариев после .RequireAuthenticatedUser()
. Вы видели их? В вашем случае вы могли бы авторизовать определенную группу пользователей AD, связав .RequireRole ("role1", "role2", "role3", ...) после .RequireAuthenticatedUser()
? Поскольку .RequireAuthenticatedUser()
и .RequireRole
добавлены в глобальный фильтр, вам больше не нужно проверять, есть ли user.IsInGroup (). Если пользователь не входит в группу, то есть не авторизован, приложение должно выдать 403, что приведет к перенаправлению на accessDeniedPath
в настройках файлов cookie из appsettings.json
.
Попался. Я пробовал что-то вроде: var policy = new AuthorizationPolicyBuilder () .RequireAuthenticatedUser () .RequireRole ("[домен] [группа]") .Build (); config.Filters.Add (новый AuthorizeFilter (политика)); раньше, но не понял, почему не работает, я думаю, это было из-за того, что некоторые группы отображаются в AD Explorer, но не в gpresult / r. После того, как я добавил RequireRole () после RequireAuthenticatedUser, я получил ошибку «Не удалось установить доверительные отношения между основным доменом и доверенным доменом», похоже, это не удалось на System.Security.Principal.NTAccount.TranslateToSids. Нужно ли мне вместо этого использовать SID?
Я никогда не видел такой ошибки. Это вышло из вашего LdapAuthenticationService
? Обратите внимание, что подход варианта 2 в основном заключается в том, чтобы сначала войти в систему с именем пользователя и паролем администратора (_config.Username
/ _config.Password
), выполнить поиск, а затем войти в систему с найденным DN пользователя и его паролем из формы входа (user.DN
/ password)
. Также обратите внимание, что .RequireAuthenticatedUser()
не выполняет аутентификацию за вас автоматически. Он просто задает задачу, чтобы ваше приложение перенаправляло на страницу входа. То же, что и .RequireRole()
. Оно фактически проверяет утверждения, которые вы создаете для объекта principal
.
Я не уверен, что это потому, что группа, которую я тестировал, отображается в проводнике AD (может быть список рассылки электронной почты), но не в gpresult / r. Другие группы, показанные в gpresult / r, которые я тестировал, работают. Я не использовал никакую другую службу аутентификации AD, только добавил RequireAuthenticatedUser () и RequireRole () для AuthorizationPolicyBuilder в ConfigureServices в Startup.cs и убедился, что аутентификация Windows верна в launchSettings.json.
Нет, если вы используете вариант 2, это должна быть проверка подлинности формы, а не проверка подлинности Windows. Вариант 1 предназначен для проверки подлинности Windows.
Если вы используете вариант 1, я не думаю, что код в разделе варианта 2 подходит для этого. С аутентификацией Windows вы в основном закончили без каких-либо дополнительных настроек. И единственная информация, которую вы можете получить, - это User.IsInRole()
и User.Identity.Name
. Из Microsoft Docs: «Когда проверка подлинности Windows включена и анонимный доступ отключен, атрибуты [Authorize] и [AllowAnonymous] не действуют». Теперь я помню, что в предыдущей версии .NET вы могли предоставить настроенные MembershipProvider
и RoleProvider
в web.config
. Не уверен, что все еще возможно в .NET Core
Я пробую вариант 1, но с кодом, обновленным в исходном сообщении вверху, я обнаружил, что если использовать любую группу AD, возвращенную с помощью команды gpresult / r, она выдаст мне новую ошибку: InvalidOperationException: не было указано authenticationScheme, и была DefaultForbidScheme не найден. Я предполагаю, что предыдущая ошибка вызвана тем, что группа должна быть для рассылки электронной почты. Итак, чтобы устранить эту новую ошибку, я добавил services.AddAuthentication (IISDefaults.AuthenticationScheme) ; в ConfigureServices теперь я получаю страницу с сообщением HTTP 403. Веб-сайт отказался отображать эту веб-страницу, думаю, это то, что я хочу.
Рассматривает авторизацию на основе утверждений