Лучшая практика для хранения требований к авторизации основного ресурса ASP.NET при аутентификации пользователей в Active Directory?
Я создаю приложение ASP.NET Core MVC для корпоративных приложений. Я хочу, чтобы мои пользователи аутентифицировались с помощью Active Directory, и я хочу, чтобы авторизации пользователей (заявки) хранились в ApplicationDbContext.
Я предполагаю, что мне нужно использовать Microsoft.AspNetCore.Identity и Microsoft.AspNetCore.Identity.EntityFrameworkCore для достижения моих целей. Какова наилучшая практика хранения требований к авторизации основного ресурса ASP.NET при аутентификации в Active Directory?
Следующий код предоставит мне доступ к текущему контексту безопасности пользователей Windows (текущему входе в систему пользователя) изнутри конвейера. Как-то мне нужно сопоставить пользователя с соответствующими утверждениями Microsoft.AspNetCore.Identity?
app.Use(async (context, next) =>
{
var identity = (ClaimsIdentity) context.User.Identity;
await next.Invoke();
});
Заранее благодарим за помощь!
Ответы
Ответ 1
Я не думаю, что в этом есть лучшая практика, но у вас есть много вариантов. Возможно, вы можете немного рассказать о том, чего вы пытаетесь достичь? Вот несколько указателей, которые могут помочь вам разобраться в этом. Просто вопрос... что вы используете для аутентификации против AD под капотом? Если вы используете IdentityServer или что-то подобное, я бы рекомендовал придерживаться варианта 4.
Вариант 1 - Сохранить претензии как свойство ApplicationUser
Прежде всего, утверждения - это просто пары ключ-значение. Это означает, что вы можете создать такую структуру, как:
public class UserClaim
{
public string UserName {get; set;}
public string Key {get; set;}
public string Value {get; set;}
}
Это позволит вам хранить претензии в классе ApplicationUser
.
Вариант 2 - Сохранение претензий с использованием UserManager
Еще лучше, вы можете использовать встроенные компоненты идентификации, введя UserManager<ApplicationUser> userManager
в класс, где вы хотите добавить претензии пользователя, а затем вызовите AddClaim()
с соответствующими значениями. Из-за системы DI в Core вы можете делать это в любом классе, который будет активирован средой выполнения.
Вариант 3 - Сохранение претензий в их собственной таблице
Другим подходом было бы увеличение класса UserClaim
с помощью свойства UserName
, а затем использование уникального идентификатора принципа (User.Identity.Name
). Затем вы можете сохранить это в DbSet<UserClaim>
в ApplicationDbContext
и получить формулы по имени пользователя.
Вариант 4 - Не хранить претензии
Если вам просто нужно получить доступ к претензиям и не хранить их (я не уверен в ваших намерениях из вашего вопроса), вам может быть лучше всего получить доступ к свойству User
, если вы находитесь в контроллере, при условии, что вы используете службу проверки подлинности, которая правильно увлажняет форму заявки.
User
украшен претензиями, принадлежащими пользователю, который подписался в ваше приложение и доступен на каждом контроллере.
В противном случае вы можете получить ClaimsPrinciple
и получить доступ к заявкам пользователя таким образом. В этом случае (вне контроллера) вы должны добавить конструктор класса IHttpContextAccessor accessor
к конструктору вашего класса и перейти к свойству HttpContextAccessor.HttpContext.User
, который снова будет ClaimsPrinciple
.
Ответ 2
Это был мой подход:
Внедрение класса MyUser
public class MyUser: IdentityUser
{
}
Используйте ASP.Net Core DbContext в отдельном "защищенном" DBC-тексте (я предпочитаю ограниченные контексты, отвечающие за их собственную функциональность)
public class SecurityDbContext : IdentityDbContext<MyUser>
{
public SecurityDbContext(DbContextOptions<SecurityDbContext> options)
: base(options)
{ }
}
Создайте трансформатор претензий. Это используется для добавления претензий из хранилища в ClaimsIdentity (объект User в вашем HttpContext).
public class ClaimsTransformer : IClaimsTransformer
{
private readonly SecurityDbContext _context;
private readonly UserManager<MyUser> _userManager;
public ClaimsTransformer(SecurityDbContext context, UserManager<MyUser> userManager)
{
_context = context;
_userManager = userManager;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var identity = context.Principal.Identity as ClaimsIdentity;
if (identity == null) return Task.FromResult(context.Principal);
try
{
if (context.Context.Response.HasStarted)
return Task.FromResult(context.Principal);
var claims = AddClaims(identity);
identity.AddClaims(claims);
}
catch (InvalidOperationException)
{
}
catch (SqlException ex)
{
if (!ex.Message.Contains("Login failed for user") && !ex.Message.StartsWith("A network-related or instance-specific error"))
throw;
}
return Task.FromResult(context.Principal);
}
private IEnumerable<Claim> AddClaims(IIdentity identity)
{
var claims = new List<Claim>();
var existing = _userManager.Users.Include(u => u.Claims).SingleOrDefault(u => u.UserName == identity.Name);
if (existing == null) return claims;
claims.Add(new Claim(SupportedClaimTypes.ModuleName, Constants.ADGroupName));
claims.AddRange(existing.Claims.Select(c => c.ToClaim()));
return claims;
}
}
Вам нужно добавить несколько вещей в свой класс startup.cs, я добавил только следующие:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<FreightRateUser, IdentityRole>(config =>
{
config.User.AllowedUserNameCharacters =
"ab[email protected]\\";
config.User.RequireUniqueEmail = false;
config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
})
.AddEntityFrameworkStores<SecurityDbContext>();
}
Не забывайте, что метод Configure
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentity();
}
Наконец, вам понадобится некоторое представление, чтобы добавить соответствующие пользовательские/требования к таблицам Identity, у меня есть контроллер с несколькими действиями (только показывая Create here):
[AllowAnonymous]
public IActionResult CreateAccess()
{
var vm = new AccessRequestViewModel
{
Username = User.Identity.Name
};
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<IActionResult> CreateAccess(AccessRequestViewModel viewModel)
{
if (User == null || !User.Identity.IsAuthenticated) return View("Index");
var newUser = new MyUser
{
UserName = viewModel.Username
};
var x = await _userManager.CreateAsync(newUser);
if (!x.Succeeded)
return View(ModelState);
var myClaims = new List<Claim>();
if (viewModel.CanManageSecurity)
myClaims.Add(new Claim(SupportedClaimTypes.Security, "SITE,LOCAL"));
if (viewModel.CanChangeExchangeRates)
myClaims.Add(new Claim(SupportedClaimTypes.ExchangeRates, "AUD,USD"));
if (viewModel.CanChangeRates)
myClaims.Add(new Claim(SupportedClaimTypes.Updates, "LOCAL"));
if (viewModel.CanManageMasterData)
myClaims.Add(new Claim(SupportedClaimTypes.Admin, "SITE,LOCAL"));
await _userManager.AddClaimsAsync(newUser, myClaims);
}
return View("Index");
}
Когда пользователь будет сохранен и снова отобразит страницу с проверкой подлинности (индекс), трансформатор претензий будет загружать претензии в ClaimsIdentity, и все должно быть хорошо.
Обратите внимание, что это для текущего пользователя HttpContext, который захочет создать для других пользователей в Create, поскольку вы, очевидно, не хотите, чтобы текущий пользователь предоставлял себе доступ.