Je crée un intranet d’entreprise ASP.NET Core MVC. Je veux que mes utilisateurs s'authentifient à l'aide d'Active Directory et je veux que les autorisations des utilisateurs (revendications) soient stockées dans ApplicationDbContext .
Je suppose que je dois utiliser Microsoft.AspNetCore.Identity et Microsoft.AspNetCore.Identity.EntityFrameworkCore pour atteindre mes objectifs. Quelle est la meilleure pratique pour stocker les revendications d’autorisation ASP.NET Core lors de l’authentification auprès de Active Directory?
Le code suivant me permettra d'accéder au contexte de sécurité actuel de l'utilisateur Windows (utilisateur actuellement connecté), à partir du pipeline. D'une manière ou d'une autre, j'ai besoin de mapper l'utilisateur avec Microsoft.AspNetCore.Identity Claims?
app.Use(async (context, next) =>
{
var identity = (ClaimsIdentity) context.User.Identity;
await next.Invoke();
});
Merci d'avance pour l'aide!
Je ne pense pas qu'il existe une meilleure pratique autour de cela, mais vous avez beaucoup d'options. Peut-être pourriez-vous élaborer un peu sur ce que vous essayez de réaliser? Voici quelques conseils qui pourraient vous aider à résoudre certains problèmes. Juste une question ... qu'utilisez-vous pour vous authentifier contre AD sous le capot? Si vous utilisez IdentityServer ou quelque chose de similaire, je vous recommanderais de vous pencher vers l'option 4.
ApplicationUser
Tout d'abord, les revendications sont simplement des paires clé-valeur. Cela signifie que vous pouvez créer une structure telle que:
public class UserClaim
{
public string UserName {get; set;}
public string Key {get; set;}
public string Value {get; set;}
}
Cela vous permettrait de stocker les revendications dans votre classe ApplicationUser
.
UserManager
Mieux encore, vous pouvez exploiter les composants d’identité intégrés en injectant un UserManager<ApplicationUser> userManager
dans la classe à laquelle vous souhaitez ajouter les revendications de l’utilisateur, puis appelez AddClaim()
avec les valeurs appropriées. En raison du système d’ID dans Core, vous êtes libre de le faire dans n’importe quelle classe qui sera activée par le moteur d’exécution.
Une autre approche consisterait à augmenter la classe UserClaim
avec une propriété UserName
, puis à utiliser l'identificateur unique du principe (User.Identity.Name
). Ensuite, vous pouvez le stocker dans un DbSet<UserClaim>
dans votre ApplicationDbContext
et récupérer les revendications par nom d'utilisateur.
Si vous avez juste besoin d'accéder aux revendications et de ne pas les stocker (je ne suis pas sûr de vos intentions à partir de votre question), alors vous feriez mieux d'accéder à la propriété User
si vous êtes dans un contrôleur, à condition que vous utilisiez un service d'authentification qui hydrate correctement les revendications.
La User
est décorée avec les revendications qui appartiennent à l'utilisateur qui s'est connecté à votre application et est disponible sur chaque contrôleur.
Vous pouvez sinon obtenir la ClaimsPrinciple
et accéder aux revendications de l'utilisateur de cette manière. Dans ce cas (en dehors d'un contrôleur), ce que vous feriez est d'ajouter un IHttpContextAccessor accessor
au constructeur de votre classe et d'accéder à la propriété HttpContextAccessor.HttpContext.User
, qui est à nouveau une ClaimsPrinciple
.
C'était mon approche:
Implémenter la classe MyUser
public class MyUser: IdentityUser
{
}
Utiliser ASP.Net Core DbContext dans un DBContext "sécurité" distinct (je préfère les contextes liés responsables de leurs propres fonctionnalités)
public class SecurityDbContext : IdentityDbContext<MyUser>
{
public SecurityDbContext(DbContextOptions<SecurityDbContext> options)
: base(options)
{ }
}
Créer un transformateur de revendications. Ceci est utilisé pour ajouter les revendications du magasin dans ClaimsIdentity (l'objet User dans votre 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;
}
}
Vous devrez ajouter quelques éléments dans votre classe startup.cs. Je n'ai ajouté que les éléments pertinents ici:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<FreightRateUser, IdentityRole>(config =>
{
config.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@\\";
config.User.RequireUniqueEmail = false;
config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
})
.AddEntityFrameworkStores<SecurityDbContext>();
}
N'oubliez pas la méthode Configure
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentity();
}
Enfin, vous aurez besoin de View pour ajouter l'utilisateur/les revendications appropriées aux tables d'identité. J'ai un contrôleur avec quelques actions (montrant uniquement la création ici):
[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");
}
Lorsque l'utilisateur est enregistré et que vous affichez à nouveau la page authentifiée (Index), le transformateur de revendications charge les revendications dans ClaimsIdentity et toutes doivent être correctes.
Remarque: ceci est destiné à l'utilisateur actuel de HttpContext, qui voudra créer pour d'autres utilisateurs de la création, car vous ne voulez évidemment pas que l'utilisateur actuel se donne accès.