Używam uwierzytelniania OWIN dla mojego projektu MVC5. To jest mójSignInAsync
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);
}
Jak widać, dodałem AccountNo
do listy roszczeń.
Jak mogę teraz zaktualizować to roszczenie w którymś momencie w mojej aplikacji? Jak dotąd mam to:
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));
}
}
kiedy próbuję usunąć roszczenie, otrzymuję ten wyjątek:
Nie można usunąć roszczenia „ http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata : 101”. Nie jest częścią tej Tożsamości lub jest roszczeniem, które jest własnością Zleceniodawcy i zawiera tę Tożsamość. Na przykład jednostka główna będzie właścicielem żądania podczas tworzenia GenericPrincipal z rolami. Role zostaną ujawnione za pośrednictwem tożsamości przekazanej w konstruktorze, ale nie będącej w rzeczywistości własnością Identity. Podobna logika istnieje dla RolePrincipal.
Czy ktoś mógłby mi pomóc dowiedzieć się, jak zaktualizować roszczenie?
SignInManager.SignInAsync
celu odświeżenia wartości roszczenia. Zobacz to pytanieOdpowiedzi:
Utworzyłem metodę rozszerzenia, aby dodać / zaktualizować / odczytać oświadczenia na podstawie danego ClaimsIdentity
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; } } }
a następnie użyć go
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"); } } }
źródło
Możesz utworzyć nowy,
ClaimsIdentity
a następnie zaktualizować roszczenia za jego pomocą.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 } ); }
źródło
Inne (asynchroniczne) podejście, wykorzystujące Identity's UserManager i SigninManager, aby odzwierciedlić zmianę w pliku cookie tożsamości (i opcjonalnie usunąć oświadczenia z tabeli bazy danych 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); } }
źródło
SignInAsync()
po założeniu roszczeń.Korzystając z najnowszej tożsamości Asp.Net z .net core 2.1, mogę aktualizować oświadczenia użytkowników z następującą logiką.
Zarejestruj się
UserClaimsPrincipalFactory
, aby za każdym razem, gdySignInManager
dołączył użytkownik, tworzone były roszczenia.Zaimplementuj niestandardowy sposób,
UserClaimsPrincipalFactory<TUser, TRole>
jak poniżejpublic 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; } }
Później w aplikacji, gdy zmienisz coś w bazie danych i chcesz to odzwierciedlić dla uwierzytelnionego i zalogowanego użytkownika, następujące wiersze osiągają to:
var user = await _userManager.GetUserAsync(User); await _signInManager.RefreshSignInAsync(user);
Dzięki temu użytkownik może zobaczyć aktualne informacje bez konieczności ponownego logowania. Wstawiłem to tuż przed zwróceniem wyniku w sterowniku aby po zakończeniu operacji wszystko bezpiecznie odświeżyło.
Zamiast edytować istniejące roszczenia i tworzyć warunki wyścigu dla bezpiecznego pliku cookie itp., Wystarczy po cichu zalogować użytkownika i odświeżyć stan :)
źródło
Ja też dostaję ten wyjątek i wyjaśniłem takie sprawy
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);
źródło
Skompilowałem kilka odpowiedzi stąd do klasy ClaimsManager wielokrotnego użytku z moimi dodatkami.
Roszczenia zostały utrwalone, plik cookie użytkownika zaktualizowany, logowanie zostało odświeżone.
Należy pamiętać, że ApplicationUser można zastąpić IdentityUser, jeśli wcześniej nie dostosowałeś. Również w moim przypadku musi mieć nieco inną logikę w środowisku programistycznym, więc możesz chcieć usunąć zależność 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>(); } }
}
źródło
kiedy używam MVC5 i dodaję tutaj roszczenie.
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; }
kiedy sprawdzam wynik roszczenia w funkcji SignInAsync, i tak nie mogę uzyskać wartości roli. Ale...
po zakończeniu tego żądania mogę uzyskać dostęp do roli w innej akcji (inne żądanie).
var userWithClaims = (ClaimsPrincipal)User; Claim CRole = userWithClaims.Claims.First(c => c.Type == ClaimTypes.Role);
więc myślę, że być może asynchroniczne powoduje aktualizację IEnumerable za procesem.
źródło
Możesz zaktualizować oświadczenia dla bieżącego użytkownika, implementując
CookieAuthenticationEvents
klasę i zastępującValidatePrincipal
. Tam możesz usunąć stare roszczenie, dodać nowe, a następnie zastąpić głównego zobowiązanego za pomocąCookieValidatePrincipalContext.ReplacePrincipal
. Nie wpływa to na żadne roszczenia przechowywane w bazie danych. To używa 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; } }
Musisz zarejestrować klasę wydarzeń w
Startup.cs
:public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddScoped<MyCookieAuthenticationEvents>(); services.ConfigureApplicationCookie(o => { o.EventsType = typeof(MyCookieAuthenticationEvents); }); }
Możesz wstrzyknąć usługi do klasy zdarzeń, aby uzyskać dostęp do nowej
AccountNo
wartości, ale zgodnie z ostrzeżeniem na tej stronie należy unikać robienia niczego zbyt kosztownego:źródło
Aby usunąć szczegóły roszczenia z bazy danych, możemy użyć poniższego kodu. Musimy również zalogować się ponownie, aby zaktualizować wartości plików cookie
// 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");
źródło
Wiele plików cookie, wiele roszczeń
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 } }
źródło
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)); }
źródło
Używam aplikacji .net core 2.2 i użyłem następującego rozwiązania: w moim statup.cs
public void ConfigureServices(IServiceCollection services) { ... services.AddIdentity<IdentityUser, IdentityRole>(options => { ... }) .AddEntityFrameworkStores<AdminDbContext>() .AddDefaultTokenProviders() .AddSignInManager();
stosowanie
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 ...
źródło
Metoda rozszerzenia działała świetnie dla mnie z jednym wyjątkiem, że jeśli użytkownik się wyloguje, stare zestawy roszczeń nadal istniały, więc z niewielką modyfikacją, ponieważ przekazywanie menedżera użytkownika przez wszystko działa świetnie i nie trzeba się wylogowywać i logować. Nie mogę odpowiedzieć bezpośrednio, ponieważ moja reputacja została odrzucona :(
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(); } }
źródło
Proszę bardzo:
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);
zaczerpnięte stąd.
źródło