Estoy usando la autenticación OWIN para mi proyecto MVC5. Este es miSignInAsync
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
var AccountNo = "101";
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.UserData, AccountNo));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, RedirectUri="Account/Index"}, identity);
}
Como puede ver, agregué AccountNo
a la lista de Reclamaciones.
Ahora, ¿cómo puedo actualizar este reclamo en algún momento de mi solicitud? Hasta ahora, tengo esto:
public string AccountNo
{
get
{
var CP = ClaimsPrincipal.Current.Identities.First();
var Account= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData);
return Account.Value;
}
set
{
var CP = ClaimsPrincipal.Current.Identities.First();
var AccountNo= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData).Value;
CP.RemoveClaim(new Claim(ClaimTypes.UserData,AccountNo));
CP.AddClaim(new Claim(ClaimTypes.UserData, value));
}
}
cuando intento eliminar el reclamo, obtengo esta excepción:
No se pudo eliminar el reclamo ' http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata : 101'. No es parte de esta Identidad o es un reclamo que es propiedad del Principal que contiene esta Identidad. Por ejemplo, el principal será el propietario de la reclamación al crear un GenericPrincipal con roles. Los roles se expondrán a través de la identidad que se pasa en el constructor, pero que en realidad no es propiedad de la identidad. Existe una lógica similar para un RolePrincipal.
¿Alguien podría ayudarme a descubrir cómo actualizar el reclamo?
SignInManager.SignInAsync
para actualizar el valor del reclamo. Vea esta preguntaRespuestas:
Creé un método de extensión para agregar / actualizar / leer reclamos basados en un ClaimsIdentity dado
namespace Foobar.Common.Extensions { public static class Extensions { public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return; // check for existing claim and remove it var existingClaim = identity.FindFirst(key); if (existingClaim != null) identity.RemoveClaim(existingClaim); // add new claim identity.AddClaim(new Claim(key, value)); var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true }); } public static string GetClaimValue(this IPrincipal currentPrincipal, string key) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return null; var claim = identity.Claims.FirstOrDefault(c => c.Type == key); return claim.Value; } } }
y luego usarlo
using Foobar.Common.Extensions; namespace Foobar.Web.Main.Controllers { public class HomeController : Controller { public ActionResult Index() { // add/updating claims User.AddUpdateClaim("key1", "value1"); User.AddUpdateClaim("key2", "value2"); User.AddUpdateClaim("key3", "value3"); } public ActionResult Details() { // reading a claim var key2 = User.GetClaim("key2"); } } }
fuente
Puede crear una nueva
ClaimsIdentity
y luego hacer la actualización de reclamaciones con ella.set { // get context of the authentication manager var authenticationManager = HttpContext.GetOwinContext().Authentication; // create a new identity from the old one var identity = new ClaimsIdentity(User.Identity); // update claim value identity.RemoveClaim(identity.FindFirst("AccountNo")); identity.AddClaim(new Claim("AccountNo", value)); // tell the authentication manager to use this new identity authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant( new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent = true } ); }
fuente
Otro enfoque (asincrónico), utilizando UserManager y SigninManager de Identity para reflejar el cambio en la cookie de Identity (y, opcionalmente, eliminar las reclamaciones de la tabla db AspNetUserClaims):
// Get User and a claims-based identity ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); var Identity = new ClaimsIdentity(User.Identity); // Remove existing claim and replace with a new value await UserManager.RemoveClaimAsync(user.Id, Identity.FindFirst("AccountNo")); await UserManager.AddClaimAsync(user.Id, new Claim("AccountNo", value)); // Re-Signin User to reflect the change in the Identity cookie await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); // [optional] remove claims from claims table dbo.AspNetUserClaims, if not needed var userClaims = UserManager.GetClaims(user.Id); if (userClaims.Any()) { foreach (var item in userClaims) { UserManager.RemoveClaim(user.Id, item); } }
fuente
SignInAsync()
después de configurar los reclamos.Usando la última identidad de Asp.Net con .net core 2.1, puedo actualizar las reclamaciones de los usuarios con la siguiente lógica.
Registre un
UserClaimsPrincipalFactory
para que cada vez queSignInManager
cante un usuario, se creen los reclamos.Implementar una costumbre
UserClaimsPrincipalFactory<TUser, TRole>
como a continuaciónpublic class UserClaimService : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole> { private readonly ApplicationDbContext _dbContext; public UserClaimService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor) { _dbContext = dbContext; } public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user) { var principal = await base.CreateAsync(user); // Get user claims from DB using dbContext // Add claims ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("claimType", "some important claim value")); return principal; } }
Más adelante en su aplicación, cuando cambie algo en la base de datos y le gustaría reflejar esto a su usuario autenticado e iniciado, las siguientes líneas logran esto:
var user = await _userManager.GetUserAsync(User); await _signInManager.RefreshSignInAsync(user);
Esto asegura que el usuario pueda ver información actualizada sin necesidad de iniciar sesión nuevamente. Puse esto justo antes de devolver el resultado en el controlador para que cuando finalice la operación, todo se actualice de forma segura.
En lugar de editar reclamos existentes y crear condiciones de carrera para cookies seguras, etc., simplemente inicie sesión en silencio y actualice el estado :)
fuente
También obtengo esa excepción y aclaro las cosas como esta.
var identity = User.Identity as ClaimsIdentity; var newIdentity = new ClaimsIdentity(identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType); newIdentity.AddClaims(identity.Claims.Where(c => false == (c.Type == claim.Type && c.Value == claim.Value))); // the claim has been removed, you can add it with a new value now if desired AuthenticationManager.SignOut(identity.AuthenticationType); AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, newIdentity);
fuente
Compilé algunas respuestas de aquí en la clase ClaimsManager reutilizable con mis adiciones.
Los reclamos persistieron, la cookie del usuario se actualizó, el inicio de sesión se actualizó.
Tenga en cuenta que ApplicationUser se puede sustituir por IdentityUser si no personalizó el anterior. Además, en mi caso, debe tener una lógica ligeramente diferente en el entorno de desarrollo, por lo que es posible que desee eliminar la dependencia de IWebHostEnvironment.
using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using YourMvcCoreProject.Models; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Hosting; namespace YourMvcCoreProject.Identity { public class ClaimsManager { private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; private readonly IWebHostEnvironment _env; private readonly ClaimsPrincipalAccessor _currentPrincipalAccessor; public ClaimsManager( ClaimsPrincipalAccessor currentPrincipalAccessor, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IWebHostEnvironment env) { _currentPrincipalAccessor = currentPrincipalAccessor; _userManager = userManager; _signInManager = signInManager; _env = env; } /// <param name="refreshSignin">Sometimes (e.g. when adding multiple claims at once) it is desirable to refresh cookie only once, for the last one </param> public async Task AddUpdateClaim(string claimType, string claimValue, bool refreshSignin = true) { await AddClaim( _currentPrincipalAccessor.ClaimsPrincipal, claimType, claimValue, async user => { await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, user, claimType); }, refreshSignin); } public async Task AddClaim(string claimType, string claimValue, bool refreshSignin = true) { await AddClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, claimValue, refreshSignin); } /// <summary> /// At certain stages of user auth there is no user yet in context but there is one to work with in client code (e.g. calling from ClaimsTransformer) /// that's why we have principal as param /// </summary> public async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, bool refreshSignin = true) { await AddClaim( principal, claimType, claimValue, async user => { // allow reassignment in dev if (_env.IsDevelopment()) await RemoveClaim(principal, user, claimType); if (GetClaim(principal, claimType) != null) throw new ClaimCantBeReassignedException(claimType); }, refreshSignin); } public async Task RemoveClaims(IEnumerable<string> claimTypes, bool refreshSignin = true) { await RemoveClaims(_currentPrincipalAccessor.ClaimsPrincipal, claimTypes, refreshSignin); } public async Task RemoveClaims(ClaimsPrincipal principal, IEnumerable<string> claimTypes, bool refreshSignin = true) { AssertAuthenticated(principal); foreach (var claimType in claimTypes) { await RemoveClaim(principal, claimType); } // reflect the change in the Identity cookie if (refreshSignin) await _signInManager.RefreshSignInAsync(await _userManager.GetUserAsync(principal)); } public async Task RemoveClaim(string claimType, bool refreshSignin = true) { await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, refreshSignin); } public async Task RemoveClaim(ClaimsPrincipal principal, string claimType, bool refreshSignin = true) { AssertAuthenticated(principal); var user = await _userManager.GetUserAsync(principal); await RemoveClaim(principal, user, claimType); // reflect the change in the Identity cookie if (refreshSignin) await _signInManager.RefreshSignInAsync(user); } private async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, Func<ApplicationUser, Task> processExistingClaims, bool refreshSignin) { AssertAuthenticated(principal); var user = await _userManager.GetUserAsync(principal); await processExistingClaims(user); var claim = new Claim(claimType, claimValue); ClaimsIdentity(principal).AddClaim(claim); await _userManager.AddClaimAsync(user, claim); // reflect the change in the Identity cookie if (refreshSignin) await _signInManager.RefreshSignInAsync(user); } /// <summary> /// Due to bugs or as result of debug it can be more than one identity of the same type. /// The method removes all the claims of a given type. /// </summary> private async Task RemoveClaim(ClaimsPrincipal principal, ApplicationUser user, string claimType) { AssertAuthenticated(principal); var identity = ClaimsIdentity(principal); var claims = identity.FindAll(claimType).ToArray(); if (claims.Length > 0) { await _userManager.RemoveClaimsAsync(user, claims); foreach (var c in claims) { identity.RemoveClaim(c); } } } private static Claim GetClaim(ClaimsPrincipal principal, string claimType) { return ClaimsIdentity(principal).FindFirst(claimType); } /// <summary> /// This kind of bugs has to be found during testing phase /// </summary> private static void AssertAuthenticated(ClaimsPrincipal principal) { if (!principal.Identity.IsAuthenticated) throw new InvalidOperationException("User should be authenticated in order to update claims"); } private static ClaimsIdentity ClaimsIdentity(ClaimsPrincipal principal) { return (ClaimsIdentity) principal.Identity; } } public class ClaimCantBeReassignedException : Exception { public ClaimCantBeReassignedException(string claimType) : base($"{claimType} can not be reassigned") { } } public class ClaimsPrincipalAccessor { private readonly IHttpContextAccessor _httpContextAccessor; public ClaimsPrincipalAccessor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public ClaimsPrincipal ClaimsPrincipal => _httpContextAccessor.HttpContext.User; } // to register dependency put this into your Startup.cs and inject ClaimsManager into Controller constructor (or other class) the in same way as you do for other dependencies public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddTransient<ClaimsPrincipalAccessor>(); services.AddTransient<ClaimsManager>(); } }
}
fuente
cuando uso MVC5 y agrego el reclamo aquí.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(PATAUserManager manager) { // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here userIdentity.AddClaim(new Claim(ClaimTypes.Role, this.Role)); return userIdentity; }
cuando verifico el resultado de la reclamación en la función SignInAsync, no puedo obtener el uso del valor del rol de todos modos. Pero...
Una vez finalizada esta solicitud, puedo acceder a Rol en otra acción (solicitud anterior).
var userWithClaims = (ClaimsPrincipal)User; Claim CRole = userWithClaims.Claims.First(c => c.Type == ClaimTypes.Role);
Entonces, creo que tal vez sea asincrónico porque IEnumerable se actualizó detrás del proceso.
fuente
Puede actualizar las reclamaciones para el usuario actual implementando una
CookieAuthenticationEvents
clase y anulandoValidatePrincipal
. Allí puede eliminar el reclamo anterior, agregar el nuevo y luego reemplazar el principal usandoCookieValidatePrincipalContext.ReplacePrincipal
. Esto no afecta ninguna reclamación almacenada en la base de datos. Esto está utilizando ASP.NET Core Identity 2.2.public class MyCookieAuthenticationEvents : CookieAuthenticationEvents { string newAccountNo = "102"; public override Task ValidatePrincipal(CookieValidatePrincipalContext context) { // first remove the old claim var claim = context.Principal.FindFirst(ClaimTypes.UserData); if (claim != null) { ((ClaimsIdentity)context.Principal.Identity).RemoveClaim(claim); } // add the new claim ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(ClaimTypes.UserData, newAccountNo)); // replace the claims context.ReplacePrincipal(context.Principal); context.ShouldRenew = true; return Task.CompletedTask; } }
Necesitas registrar la clase de eventos en
Startup.cs
:public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddScoped<MyCookieAuthenticationEvents>(); services.ConfigureApplicationCookie(o => { o.EventsType = typeof(MyCookieAuthenticationEvents); }); }
Puede inyectar servicios en la clase de eventos para acceder al nuevo
AccountNo
valor, pero según la advertencia de esta página , debe evitar hacer algo demasiado caro:fuente
Para eliminar los detalles del reclamo de la base de datos, podemos usar el siguiente código. Además, debemos iniciar sesión nuevamente para actualizar los valores de las cookies.
// create a new identity var identity = new ClaimsIdentity(User.Identity); // Remove the existing claim value of current user from database if(identity.FindFirst("NameOfUser")!=null) await UserManager.RemoveClaimAsync(applicationUser.Id, identity.FindFirst("NameOfUser")); // Update customized claim await UserManager.AddClaimAsync(applicationUser.Id, new Claim("NameOfUser", applicationUser.Name)); // the claim has been updates, We need to change the cookie value for getting the updated claim AuthenticationManager.SignOut(identity.AuthenticationType); await SignInManager.SignInAsync(Userprofile, isPersistent: false, rememberBrowser: false); return RedirectToAction("Index", "Home");
fuente
Múltiples cookies, múltiples reclamos
public class ClaimsCookie { private readonly ClaimsPrincipal _user; private readonly HttpContext _httpContext; public ClaimsCookie(ClaimsPrincipal user, HttpContext httpContext = null) { _user = user; _httpContext = httpContext; } public string GetValue(CookieName cookieName, KeyName keyName) { var principal = _user as ClaimsPrincipal; var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString()); return cp.FindFirst(((KeyName)keyName).ToString()).Value; } public async void SetValue(CookieName cookieName, KeyName[] keyName, string[] value) { if (keyName.Length != value.Length) { return; } var principal = _user as ClaimsPrincipal; var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString()); for (int i = 0; i < keyName.Length; i++) { if (cp.FindFirst(((KeyName)keyName[i]).ToString()) != null) { cp.RemoveClaim(cp.FindFirst(((KeyName)keyName[i]).ToString())); cp.AddClaim(new Claim(((KeyName)keyName[i]).ToString(), value[i])); } } await _httpContext.SignOutAsync(CookieName.UserProfilCookie.ToString()); await _httpContext.SignInAsync(CookieName.UserProfilCookie.ToString(), new ClaimsPrincipal(cp), new AuthenticationProperties { IsPersistent = bool.Parse(cp.FindFirst(KeyName.IsPersistent.ToString()).Value), AllowRefresh = true }); } public enum CookieName { CompanyUserProfilCookie = 0, UserProfilCookie = 1, AdminPanelCookie = 2 } public enum KeyName { Id, Name, Surname, Image, IsPersistent } }
fuente
if (HttpContext.User.Identity is ClaimsIdentity identity) { identity.RemoveClaim(identity.FindFirst("userId")); identity.AddClaim(new Claim("userId", userInfo?.id.ToString())); await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(HttpContext.User.Identity)); }
fuente
Estoy usando una aplicación .net core 2.2 y utilicé la siguiente solución: en mi statup.cs
public void ConfigureServices(IServiceCollection services) { ... services.AddIdentity<IdentityUser, IdentityRole>(options => { ... }) .AddEntityFrameworkStores<AdminDbContext>() .AddDefaultTokenProviders() .AddSignInManager();
uso
private readonly SignInManager<IdentityUser> _signInManager; public YourController( ..., SignInManager<IdentityUser> signInManager) { ... _signInManager = signInManager; } public async Task<IActionResult> YourMethod() // <-NOTE IT IS ASYNC { var user = _userManager.FindByNameAsync(User.Identity.Name).Result; var claimToUse = ClaimsHelpers.CreateClaim(ClaimTypes.ActiveCompany, JsonConvert.SerializeObject(cc)); var claimToRemove = _userManager.GetClaimsAsync(user).Result .FirstOrDefault(x => x.Type == ClaimTypes.ActiveCompany.ToString()); if (claimToRemove != null) { var result = _userManager.ReplaceClaimAsync(user, claimToRemove, claimToUse).Result; await _signInManager.RefreshSignInAsync(user); //<--- THIS } else ...
fuente
El método de extensión funcionó muy bien para mí, con una excepción de que si el usuario cierra la sesión, los conjuntos de reclamos antiguos todavía existían, por lo que con una pequeña modificación, como pasar el administrador de usuarios a través de todo, funciona muy bien y no es necesario cerrar la sesión e iniciar sesión. No puedo responder directamente porque mi reputación ha sido criticada :(
public static class ClaimExtensions { public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value, ApplicationUserManager userManager) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return; // check for existing claim and remove it var existingClaim = identity.FindFirst(key); if (existingClaim != null) { RemoveClaim(currentPrincipal, key, userManager); } // add new claim var claim = new Claim(key, value); identity.AddClaim(claim); var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true }); //Persist to store userManager.AddClaim(identity.GetUserId(),claim); } public static void RemoveClaim(this IPrincipal currentPrincipal, string key, ApplicationUserManager userManager) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return ; // check for existing claim and remove it var existingClaims = identity.FindAll(key); existingClaims.ForEach(c=> identity.RemoveClaim(c)); //remove old claims from store var user = userManager.FindById(identity.GetUserId()); var claims = userManager.GetClaims(user.Id); claims.Where(x => x.Type == key).ToList().ForEach(c => userManager.RemoveClaim(user.Id, c)); } public static string GetClaimValue(this IPrincipal currentPrincipal, string key) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return null; var claim = identity.Claims.First(c => c.Type == key); return claim.Value; } public static string GetAllClaims(this IPrincipal currentPrincipal, ApplicationUserManager userManager) { var identity = currentPrincipal.Identity as ClaimsIdentity; if (identity == null) return null; var claims = userManager.GetClaims(identity.GetUserId()); var userClaims = new StringBuilder(); claims.ForEach(c => userClaims.AppendLine($"<li>{c.Type}, {c.Value}</li>")); return userClaims.ToString(); } }
fuente
Aqui tienes:
var user = User as ClaimsPrincipal; var identity = user.Identity as ClaimsIdentity; var claim = (from c in user.Claims where c.Type == ClaimTypes.UserData select c).Single(); identity.RemoveClaim(claim);
tomado de aquí.
fuente